From 68eb5a1df5afdc011aa467f1c723a8792532dd3f Mon Sep 17 00:00:00 2001 From: Robbin Ehn Date: Wed, 21 Feb 2024 08:58:19 +0000 Subject: [PATCH 01/38] 8321075: RISC-V: UseSystemMemoryBarrier lacking proper OS support Reviewed-by: fyang, yadongwang, luhenry --- src/hotspot/os/linux/os_linux.cpp | 25 +++++++++++++++++++ src/hotspot/os/linux/os_linux.hpp | 2 ++ .../os/linux/systemMemoryBarrier_linux.cpp | 14 ++++++++++- 3 files changed, 40 insertions(+), 1 deletion(-) diff --git a/src/hotspot/os/linux/os_linux.cpp b/src/hotspot/os/linux/os_linux.cpp index 36c8c8f9b4369..c51aeb0ae17c1 100644 --- a/src/hotspot/os/linux/os_linux.cpp +++ b/src/hotspot/os/linux/os_linux.cpp @@ -85,6 +85,8 @@ #endif // put OS-includes here +# include +# include # include # include # include @@ -342,6 +344,29 @@ static void next_line(FILE *f) { } while (c != '\n' && c != EOF); } +void os::Linux::kernel_version(long* major, long* minor) { + *major = -1; + *minor = -1; + + struct utsname buffer; + int ret = uname(&buffer); + if (ret != 0) { + log_warning(os)("uname(2) failed to get kernel version: %s", os::errno_name(ret)); + return; + } + + char* walker = buffer.release; + long* set_v = major; + while (*minor == -1 && walker != nullptr) { + if (isdigit(walker[0])) { + *set_v = strtol(walker, &walker, 10); + set_v = minor; + } else { + ++walker; + } + } +} + bool os::Linux::get_tick_information(CPUPerfTicks* pticks, int which_logical_cpu) { FILE* fh; uint64_t userTicks, niceTicks, systemTicks, idleTicks; diff --git a/src/hotspot/os/linux/os_linux.hpp b/src/hotspot/os/linux/os_linux.hpp index 4b2ccf8e370db..6b902e8280244 100644 --- a/src/hotspot/os/linux/os_linux.hpp +++ b/src/hotspot/os/linux/os_linux.hpp @@ -93,6 +93,8 @@ class os::Linux { bool has_steal_ticks; }; + static void kernel_version(long* major, long* minor); + // which_logical_cpu=-1 returns accumulated ticks for all cpus. static bool get_tick_information(CPUPerfTicks* pticks, int which_logical_cpu); static bool _stack_is_executable; diff --git a/src/hotspot/os/linux/systemMemoryBarrier_linux.cpp b/src/hotspot/os/linux/systemMemoryBarrier_linux.cpp index 446449a40e094..892d825b40cdc 100644 --- a/src/hotspot/os/linux/systemMemoryBarrier_linux.cpp +++ b/src/hotspot/os/linux/systemMemoryBarrier_linux.cpp @@ -24,7 +24,7 @@ #include "precompiled.hpp" #include "logging/log.hpp" -#include "runtime/os.hpp" +#include "os_linux.hpp" #include "utilities/debug.hpp" #include "utilities/systemMemoryBarrier.hpp" @@ -61,6 +61,18 @@ static long membarrier(int cmd, unsigned int flags, int cpu_id) { } bool LinuxSystemMemoryBarrier::initialize() { +#if defined(RISCV) +// RISCV port was introduced in kernel 4.4. +// 4.4 also made membar private expedited mandatory. +// But RISCV actually don't support it until 6.9. + long major, minor; + os::Linux::kernel_version(&major, &minor); + if (!(major > 6 || (major == 6 && minor >= 9))) { + log_info(os)("Linux kernel %ld.%ld does not support MEMBARRIER PRIVATE_EXPEDITED on RISC-V.", + major, minor); + return false; + } +#endif long ret = membarrier(MEMBARRIER_CMD_QUERY, 0, 0); if (ret < 0) { log_info(os)("MEMBARRIER_CMD_QUERY unsupported"); From 921507c51062e82e55ade43262e7eb8036ea4bd6 Mon Sep 17 00:00:00 2001 From: Albert Mingkun Yang Date: Wed, 21 Feb 2024 10:27:55 +0000 Subject: [PATCH 02/38] 8326319: G1: Remove unused G1ConcurrentMark::_init_times Reviewed-by: tschatzl, kbarrett --- src/hotspot/share/gc/g1/g1ConcurrentMark.cpp | 4 +--- src/hotspot/share/gc/g1/g1ConcurrentMark.hpp | 1 - 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/hotspot/share/gc/g1/g1ConcurrentMark.cpp b/src/hotspot/share/gc/g1/g1ConcurrentMark.cpp index 723b26c4bf393..cab9f52a009cf 100644 --- a/src/hotspot/share/gc/g1/g1ConcurrentMark.cpp +++ b/src/hotspot/share/gc/g1/g1ConcurrentMark.cpp @@ -480,7 +480,6 @@ G1ConcurrentMark::G1ConcurrentMark(G1CollectedHeap* g1h, // _verbose_level set below - _init_times(), _remark_times(), _remark_mark_times(), _remark_weak_ref_times(), @@ -2114,7 +2113,6 @@ void G1ConcurrentMark::print_summary_info() { } log.trace(" Concurrent marking:"); - print_ms_time_info(" ", "init marks", _init_times); print_ms_time_info(" ", "remarks", _remark_times); { print_ms_time_info(" ", "final marks", _remark_mark_times); @@ -2125,7 +2123,7 @@ void G1ConcurrentMark::print_summary_info() { log.trace(" Finalize live data total time = %8.2f s (avg = %8.2f ms).", _cleanup_times.sum() / 1000.0, _cleanup_times.avg()); log.trace(" Total stop_world time = %8.2f s.", - (_init_times.sum() + _remark_times.sum() + _cleanup_times.sum())/1000.0); + (_remark_times.sum() + _cleanup_times.sum())/1000.0); log.trace(" Total concurrent time = %8.2f s (%8.2f s marking).", cm_thread()->vtime_accum(), cm_thread()->vtime_mark_accum()); } diff --git a/src/hotspot/share/gc/g1/g1ConcurrentMark.hpp b/src/hotspot/share/gc/g1/g1ConcurrentMark.hpp index 60d14984ca004..ccd35bed64f06 100644 --- a/src/hotspot/share/gc/g1/g1ConcurrentMark.hpp +++ b/src/hotspot/share/gc/g1/g1ConcurrentMark.hpp @@ -435,7 +435,6 @@ class G1ConcurrentMark : public CHeapObj { G1OldTracer* _gc_tracer_cm; // Timing statistics. All of them are in ms - NumberSeq _init_times; NumberSeq _remark_times; NumberSeq _remark_mark_times; NumberSeq _remark_weak_ref_times; From 5f16f342d9be955b87054bf4b6369ed47cca964d Mon Sep 17 00:00:00 2001 From: Claes Redestad Date: Wed, 21 Feb 2024 11:19:37 +0000 Subject: [PATCH 03/38] 8326370: Remove redundant and misplaced micros from StringBuffers Reviewed-by: shade --- .../bench/java/lang/StringBuffers.java | 38 ------------------- 1 file changed, 38 deletions(-) diff --git a/test/micro/org/openjdk/bench/java/lang/StringBuffers.java b/test/micro/org/openjdk/bench/java/lang/StringBuffers.java index fea64c0d2e02b..163a5bdd39fc2 100644 --- a/test/micro/org/openjdk/bench/java/lang/StringBuffers.java +++ b/test/micro/org/openjdk/bench/java/lang/StringBuffers.java @@ -29,7 +29,6 @@ import org.openjdk.jmh.annotations.Mode; import org.openjdk.jmh.annotations.OutputTimeUnit; import org.openjdk.jmh.annotations.Scope; -import org.openjdk.jmh.annotations.Setup; import org.openjdk.jmh.annotations.State; import org.openjdk.jmh.annotations.Warmup; @@ -43,43 +42,6 @@ @Fork(3) public class StringBuffers { - private String name; - private String blaha; - private Sigurd sig; - - @Setup - public void setup() { - name = "joe"; - blaha = "sniglogigloienlitenapasomarengrodasjukadjavelhej"; - sig = new Sigurd(); - } - - @Benchmark - public String appendAndToString() { - return "MyStringBuffer named:" + ((name == null) ? "unknown" : name) + "."; - } - - @Benchmark - public String toStringComplex() { - return sig.toString(); - } - - static class Sigurd { - int x; - byte y; - String z = "yahoo"; - - @Override - public String toString() { - return Integer.toString(x) + "_" + Integer.toString((int) y) + "_" + z + "_"; - } - } - - @Benchmark - public String substring() { - return blaha.substring(30, 35); - } - StringBuffer sb = new StringBuffer(); @Benchmark From 492e8bf563135d27b46fde198880e62d5f1940e8 Mon Sep 17 00:00:00 2001 From: Aleksey Shipilev Date: Wed, 21 Feb 2024 11:48:59 +0000 Subject: [PATCH 04/38] 8325587: Shenandoah: ShenandoahLock should allow blocking in VM Reviewed-by: rehn, rkennke --- .../share/gc/shenandoah/shenandoahHeap.cpp | 6 ++- .../share/gc/shenandoah/shenandoahLock.cpp | 38 +++++++++++++++++ .../share/gc/shenandoah/shenandoahLock.hpp | 41 +++++++++++-------- 3 files changed, 66 insertions(+), 19 deletions(-) diff --git a/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp b/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp index 68ae5f6ecb4da..f0a2e53a12b1e 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp @@ -1003,7 +1003,11 @@ HeapWord* ShenandoahHeap::allocate_memory(ShenandoahAllocRequest& req) { } HeapWord* ShenandoahHeap::allocate_memory_under_lock(ShenandoahAllocRequest& req, bool& in_new_region) { - ShenandoahHeapLocker locker(lock()); + // If we are dealing with mutator allocation, then we may need to block for safepoint. + // We cannot block for safepoint for GC allocations, because there is a high chance + // we are already running at safepoint or from stack watermark machinery, and we cannot + // block again. + ShenandoahHeapLocker locker(lock(), req.is_mutator_alloc()); return _free_set->allocate(req, in_new_region); } diff --git a/src/hotspot/share/gc/shenandoah/shenandoahLock.cpp b/src/hotspot/share/gc/shenandoah/shenandoahLock.cpp index 3a9b612860b9f..39588d68bc83b 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahLock.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahLock.cpp @@ -28,9 +28,47 @@ #include "gc/shenandoah/shenandoahLock.hpp" #include "runtime/atomic.hpp" +#include "runtime/interfaceSupport.inline.hpp" #include "runtime/javaThread.hpp" #include "runtime/os.inline.hpp" +// These are inline variants of Thread::SpinAcquire with optional blocking in VM. + +class ShenandoahNoBlockOp : public StackObj { +public: + ShenandoahNoBlockOp(JavaThread* java_thread) { + assert(java_thread == nullptr, "Should not pass anything"); + } +}; + +void ShenandoahLock::contended_lock(bool allow_block_for_safepoint) { + Thread* thread = Thread::current(); + if (allow_block_for_safepoint && thread->is_Java_thread()) { + contended_lock_internal(JavaThread::cast(thread)); + } else { + contended_lock_internal(nullptr); + } +} + +template +void ShenandoahLock::contended_lock_internal(JavaThread* java_thread) { + int ctr = 0; + int yields = 0; + while (Atomic::cmpxchg(&_state, unlocked, locked) != unlocked) { + if ((++ctr & 0xFFF) == 0) { + BlockOp block(java_thread); + if (yields > 5) { + os::naked_short_sleep(1); + } else { + os::naked_yield(); + yields++; + } + } else { + SpinPause(); + } + } +} + ShenandoahSimpleLock::ShenandoahSimpleLock() { assert(os::mutex_init_done(), "Too early!"); } diff --git a/src/hotspot/share/gc/shenandoah/shenandoahLock.hpp b/src/hotspot/share/gc/shenandoah/shenandoahLock.hpp index 6237637619548..4412680bc8ce6 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahLock.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahLock.hpp @@ -35,34 +35,39 @@ class ShenandoahLock { enum LockState { unlocked = 0, locked = 1 }; shenandoah_padding(0); - volatile int _state; + volatile LockState _state; shenandoah_padding(1); volatile Thread* _owner; shenandoah_padding(2); + template + void contended_lock_internal(JavaThread* java_thread); + public: ShenandoahLock() : _state(unlocked), _owner(nullptr) {}; - void lock() { -#ifdef ASSERT - assert(_owner != Thread::current(), "reentrant locking attempt, would deadlock"); -#endif - Thread::SpinAcquire(&_state, "Shenandoah Heap Lock"); -#ifdef ASSERT - assert(_state == locked, "must be locked"); - assert(_owner == nullptr, "must not be owned"); - _owner = Thread::current(); -#endif + void lock(bool allow_block_for_safepoint) { + assert(Atomic::load(&_owner) != Thread::current(), "reentrant locking attempt, would deadlock"); + + // Try to lock fast, or dive into contended lock handling. + if (Atomic::cmpxchg(&_state, unlocked, locked) != unlocked) { + contended_lock(allow_block_for_safepoint); + } + + assert(Atomic::load(&_state) == locked, "must be locked"); + assert(Atomic::load(&_owner) == nullptr, "must not be owned"); + DEBUG_ONLY(Atomic::store(&_owner, Thread::current());) } void unlock() { -#ifdef ASSERT - assert (_owner == Thread::current(), "sanity"); - _owner = nullptr; -#endif - Thread::SpinRelease(&_state); + assert(Atomic::load(&_owner) == Thread::current(), "sanity"); + DEBUG_ONLY(Atomic::store(&_owner, (Thread*)nullptr);) + OrderAccess::fence(); + Atomic::store(&_state, unlocked); } + void contended_lock(bool allow_block_for_safepoint); + bool owned_by_self() { #ifdef ASSERT return _state == locked && _owner == Thread::current(); @@ -77,9 +82,9 @@ class ShenandoahLocker : public StackObj { private: ShenandoahLock* const _lock; public: - ShenandoahLocker(ShenandoahLock* lock) : _lock(lock) { + ShenandoahLocker(ShenandoahLock* lock, bool allow_block_for_safepoint = false) : _lock(lock) { if (_lock != nullptr) { - _lock->lock(); + _lock->lock(allow_block_for_safepoint); } } From 23522682d4bcf9592682007909a74c5cf0b067c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20Gr=C3=B6nlund?= Date: Wed, 21 Feb 2024 13:38:36 +0000 Subject: [PATCH 05/38] 8326334: JFR failed assert(used(klass)) failed: invariant Reviewed-by: egahlin --- src/hotspot/share/jfr/support/jfrKlassUnloading.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/hotspot/share/jfr/support/jfrKlassUnloading.cpp b/src/hotspot/share/jfr/support/jfrKlassUnloading.cpp index 46d9cea90e90c..e0f26a800ba52 100644 --- a/src/hotspot/share/jfr/support/jfrKlassUnloading.cpp +++ b/src/hotspot/share/jfr/support/jfrKlassUnloading.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -101,13 +101,12 @@ void JfrKlassUnloading::clear() { } } -static bool add_to_unloaded_klass_set(traceid klass_id, bool current_epoch) { +static void add_to_unloaded_klass_set(traceid klass_id) { assert_locked_or_safepoint(ClassLoaderDataGraph_lock); - GrowableArray* const unload_set = current_epoch ? get_unload_set() : get_unload_set_previous_epoch(); + GrowableArray* const unload_set = get_unload_set(); assert(unload_set != nullptr, "invariant"); assert(unload_set->find(klass_id) == -1, "invariant"); unload_set->append(klass_id); - return true; } #if INCLUDE_MANAGEMENT @@ -129,7 +128,8 @@ bool JfrKlassUnloading::on_unload(const Klass* k) { if (IS_JDK_JFR_EVENT_SUBKLASS(k)) { ++event_klass_unloaded_count; } - return USED_ANY_EPOCH(k) && add_to_unloaded_klass_set(JfrTraceId::load_raw(k), USED_THIS_EPOCH(k)); + add_to_unloaded_klass_set(JfrTraceId::load_raw(k)); + return USED_THIS_EPOCH(k); } bool JfrKlassUnloading::is_unloaded(traceid klass_id, bool previous_epoch /* false */) { From 33834b7d14de8cca1587d8405d13aec669b6cc23 Mon Sep 17 00:00:00 2001 From: Magnus Ihse Bursie Date: Wed, 21 Feb 2024 13:44:12 +0000 Subject: [PATCH 06/38] 8326375: [REDO] Clean up NativeCompilation.gmk and its newly created parts Reviewed-by: jwaters, erikj --- make/common/NativeCompilation.gmk | 120 ++++++------ make/common/native/CompileFile.gmk | 8 +- make/common/native/DebugSymbols.gmk | 17 -- make/common/native/Flags.gmk | 40 ++-- make/common/native/Link.gmk | 292 ++++++++++++++-------------- make/common/native/Paths.gmk | 22 ++- 6 files changed, 252 insertions(+), 247 deletions(-) diff --git a/make/common/NativeCompilation.gmk b/make/common/NativeCompilation.gmk index b2bbfff1f7f33..b9ea519cc3f61 100644 --- a/make/common/NativeCompilation.gmk +++ b/make/common/NativeCompilation.gmk @@ -25,8 +25,8 @@ ################################################################################ # This is the top-level entry point for our native compilation and linking. -# It contains the SetupNativeCompilation function, but is supported by helper -# functions in the make/common/native directory. +# It contains the SetupNativeCompilation macro, but is supported by helper +# macros in the make/common/native directory. ################################################################################ ifndef _NATIVE_COMPILATION_GMK @@ -129,23 +129,25 @@ include native/ToolchainDefinitions.gmk # SetupNativeCompilation = $(NamedParamsMacroTemplate) define SetupNativeCompilationBody - # Setup variables for the rest of the function to work with - $$(eval $$(call SetupBasicVariables1,$1)) - $$(eval $$(call SetupDebugSymbols,$1)) - $$(eval $$(call SetupBasicVariables2,$1)) + # When reading this code, note that macros named Setup are just setting + # variables, and macros called Create are setting up rules to create + # files. Macros starting with any other verb are more complicated, and can do + # all of the above, and also call directly to the shell. + + ### + ### Prepare for compilation and linking + ### + $$(eval $$(call VerifyArguments,$1)) - $$(eval $$(call SetupBasicVariables3,$1)) - # Need to make sure TARGET is first on list - $1 := $$($1_TARGET) + # Setup variables for the rest of this macro to work with + $$(eval $$(call SetupBasicVariables,$1)) # Setup the toolchain to be used $$(eval $$(call SetupToolchain,$1)) - # Figure out all source files to compile - $$(eval $$(call LocateSourceFiles,$1)) - - # ... and what the output object files will be + # Find all source files to compile and determine the output object file names + $$(eval $$(call SetupSourceFiles,$1)) $$(eval $$(call SetupOutputFiles,$1)) # Setup CFLAGS/CXXFLAGS based on warnings, optimizations, extra flags etc. @@ -154,20 +156,29 @@ define SetupNativeCompilationBody # Machinery needed for the build to function properly $$(eval $$(call SetupBuildSystemSupport,$1)) + $$(eval $$(call RemoveSuperfluousOutputFiles,$1)) + + # Need to make sure TARGET is first on list before starting to create files + $1 := $$($1_TARGET) + + # Have make print information about the library when we start compiling + $$(eval $$(call PrintStartInfo,$1)) + + ### + ### Compile all native source code files + ### + # Create a PCH, if requested $$(eval $$(call CreatePrecompiledHeader,$1)) - # Now call SetupCompileNativeFile for each source file we are going to compile. + # Now call CreateCompiledNativeFile for each source file we are going to compile. $$(foreach file, $$($1_SRCS), \ - $$(eval $$(call SetupCompileNativeFile,$1_$$(notdir $$(file)),\ + $$(eval $$(call CreateCompiledNativeFile,$1_$$(notdir $$(file)),\ FILE := $$(file), \ BASE := $1, \ )) \ ) - # Have make print information about the library when we start compiling - $$(eval $$(call PrintInfo,$1)) - ifeq ($(call isTargetOs, windows), true) # On windows we need to create a resource file $$(eval $$(call CreateWindowsResourceFile,$1)) @@ -178,15 +189,16 @@ define SetupNativeCompilationBody $$(eval $$(call CreateDependencyFile,$1)) $$(eval $$(call ImportDependencyFile,$1)) - # Prepare for linking - $$(eval $$(call SetupLinkerFlags,$1)) - - $$(eval $$(call SetupMapfile,$1)) + ### + ### Link the object files into a native output library/executable + ### # Handle native debug symbols $$(eval $$(call CreateDebugSymbols,$1)) - $$(eval $$(call SetupStrip,$1)) + # Prepare for linking + $$(eval $$(call SetupLinkerFlags,$1)) + $$(eval $$(call SetupLinking,$1)) $$(eval $$(call SetupObjectFileList,$1)) @@ -200,8 +212,30 @@ define SetupNativeCompilationBody endef ################################################################################ -# Setup basic variables, part 1 -define SetupBasicVariables1 +# Verify that user passed arguments are valid +define VerifyArguments + ifneq ($$($1_NAME), $(basename $$($1_NAME))) + $$(error NAME must not contain any directory path in $1) + endif + ifneq ($(findstring $$($1_SUFFIX), $$($1_NAME)), ) + $$(error NAME should be specified without suffix: $$($1_SUFFIX) in $1) + endif + ifneq ($(findstring $$($1_PREFIX), $$($1_NAME)), ) + $$(error NAME should be specified without prefix: $$($1_PREFIX) in $1) + endif + ifeq ($$($1_OUTPUT_DIR), ) + $$(error OUTPUT_DIR is missing in $1) + endif + ifneq ($$($1_MANIFEST), ) + ifeq ($$($1_MANIFEST_VERSION), ) + $$(error If MANIFEST is provided, then MANIFEST_VERSION is required in $1) + endif + endif +endef + +################################################################################ +# Setup basic variables +define SetupBasicVariables # If type is unspecified, default to LIBRARY ifeq ($$($1_TYPE), ) $1_TYPE := LIBRARY @@ -214,11 +248,7 @@ define SetupBasicVariables1 $1_TYPE := STATIC_LIBRARY endif endif -endef -################################################################################ -# Setup basic variables, part 2 -define SetupBasicVariables2 # STATIC_LIBS is set from Main.gmk when building static versions of certain # native libraries. ifeq ($(STATIC_LIBS), true) @@ -247,33 +277,7 @@ define SetupBasicVariables2 endif endif endif -endef - -################################################################################ -# Verify that user passed arguments are valid -define VerifyArguments - ifneq ($$($1_NAME), $(basename $$($1_NAME))) - $$(error NAME must not contain any directory path in $1) - endif - ifneq ($(findstring $$($1_SUFFIX), $$($1_NAME)), ) - $$(error NAME should be specified without suffix: $$($1_SUFFIX) in $1) - endif - ifneq ($(findstring $$($1_PREFIX), $$($1_NAME)), ) - $$(error NAME should be specified without prefix: $$($1_PREFIX) in $1) - endif - ifeq ($$($1_OUTPUT_DIR), ) - $$(error OUTPUT_DIR is missing in $1) - endif - ifneq ($$($1_MANIFEST), ) - ifeq ($$($1_MANIFEST_VERSION), ) - $$(error If MANIFEST is provided, then MANIFEST_VERSION is required in $1) - endif - endif -endef -################################################################################ -# Setup basic variables, part 3 -define SetupBasicVariables3 $1_BASENAME := $$($1_PREFIX)$$($1_NAME)$$($1_SUFFIX) $1_TARGET := $$($1_OUTPUT_DIR)/$$($1_BASENAME) $1_NOSUFFIX := $$($1_PREFIX)$$($1_NAME) @@ -283,8 +287,6 @@ endef ################################################################################ # Setup machinery needed by the build system define SetupBuildSystemSupport - $1_BUILD_INFO := $$($1_OBJECT_DIR)/_build-info.marker - # Track variable changes for all variables that affect the compilation command # lines for all object files in this setup. This includes at least all the # variables used in the call to add_native_source below. @@ -297,13 +299,15 @@ endef ################################################################################ # Have make print information about the library when we start compiling -define PrintInfo +define PrintStartInfo # Setup rule for printing progress info when compiling source files. # This is a rough heuristic and may not always print accurate information. # The $1_BUILD_INFO and $1_BUILD_INFO_DEPS variables are used in # TestFilesCompilation.gmk. $$(call SetIfEmpty, $1_BUILD_INFO_LOG_MACRO, LogWarn) $1_BUILD_INFO_DEPS := $$($1_SRCS) $$($1_COMPILE_VARDEPS_FILE) + $1_BUILD_INFO := $$($1_OBJECT_DIR)/_build-info.marker + $$($1_BUILD_INFO): $$($1_BUILD_INFO_DEPS) ifeq ($$(wildcard $$($1_TARGET)), ) $$(call $$($1_BUILD_INFO_LOG_MACRO), \ diff --git a/make/common/native/CompileFile.gmk b/make/common/native/CompileFile.gmk index c437746847287..a9384fb0cf509 100644 --- a/make/common/native/CompileFile.gmk +++ b/make/common/native/CompileFile.gmk @@ -100,8 +100,8 @@ DEPENDENCY_TARGET_SED_PATTERN := \ # FILE - The full path of the source file to compiler # BASE - The name of the rule for the entire binary to build ($1) # -SetupCompileNativeFile = $(NamedParamsMacroTemplate) -define SetupCompileNativeFileBody +CreateCompiledNativeFile = $(NamedParamsMacroTemplate) +define CreateCompiledNativeFileBody $1_FILENAME := $$(notdir $$($1_FILE)) # The target file to be generated. @@ -120,7 +120,7 @@ define SetupCompileNativeFileBody # This is the definite source file to use for $1_FILENAME. $1_SRC_FILE := $$($1_FILE) - $$(eval $$(call SetupCompileFileFlags,$1)) + $$(eval $$(call SetupCompileFileFlags,$1,$$($1_BASE))) ifneq ($$(filter %.c, $$($1_FILENAME)), ) # Compile as a C file @@ -250,7 +250,7 @@ define CreatePrecompiledHeader $1_GENERATED_PCH_SRC := $$($1_OBJECT_DIR)/$1_pch.cpp $1_GENERATED_PCH_OBJ := $$($1_OBJECT_DIR)/$1_pch$(OBJ_SUFFIX) - $$(eval $$(call SetupCompileNativeFile, $1_$$(notdir $$($1_GENERATED_PCH_SRC)), \ + $$(eval $$(call CreateCompiledNativeFile, $1_$$(notdir $$($1_GENERATED_PCH_SRC)), \ FILE := $$($1_GENERATED_PCH_SRC), \ BASE := $1, \ EXTRA_CXXFLAGS := -Fp$$($1_PCH_FILE) -Yc$$(notdir $$($1_PRECOMPILED_HEADER)), \ diff --git a/make/common/native/DebugSymbols.gmk b/make/common/native/DebugSymbols.gmk index ba9db08a1ce5d..f526c8d4ee321 100644 --- a/make/common/native/DebugSymbols.gmk +++ b/make/common/native/DebugSymbols.gmk @@ -26,23 +26,6 @@ ################################################################################ # This file contains functionality related to native debug symbol handling. -################################################################################ -define SetupDebugSymbols - $$(call SetIfEmpty, $1_COMPILE_WITH_DEBUG_SYMBOLS, $$(COMPILE_WITH_DEBUG_SYMBOLS)) - - ifeq ($(STATIC_LIBS), true) - # For release builds where debug symbols are configured to be moved to - # separate debuginfo files, disable debug symbols for static libs instead. - # We don't currently support this configuration and we don't want symbol - # information in release builds unless explicitly asked to provide it. - ifeq ($(DEBUG_LEVEL), release) - ifeq ($(COPY_DEBUG_SYMBOLS), true) - $1_COMPILE_WITH_DEBUG_SYMBOLS := false - endif - endif - endif -endef - ################################################################################ define CreateDebugSymbols ifneq ($$($1_COPY_DEBUG_SYMBOLS), false) diff --git a/make/common/native/Flags.gmk b/make/common/native/Flags.gmk index ed0adba33ba29..213312047a4ff 100644 --- a/make/common/native/Flags.gmk +++ b/make/common/native/Flags.gmk @@ -29,10 +29,12 @@ # like optimization level. ################################################################################ +# $1 is the prefix of the file to be compiled +# $2 is the prefix of the library, i.e. $$($1_BASE) define SetupCompileFileFlags ifeq ($$($1_OPTIMIZATION), ) - $1_OPT_CFLAGS := $$($$($1_BASE)_OPT_CFLAGS) - $1_OPT_CXXFLAGS := $$($$($1_BASE)_OPT_CXXFLAGS) + $1_OPT_CFLAGS := $$($2_OPT_CFLAGS) + $1_OPT_CXXFLAGS := $$($2_OPT_CXXFLAGS) else ifeq ($$($1_OPTIMIZATION), NONE) $1_OPT_CFLAGS := $(C_O_FLAG_NONE) @@ -57,23 +59,23 @@ define SetupCompileFileFlags endif endif - ifneq ($$($$($1_BASE)_PRECOMPILED_HEADER), ) - ifeq ($$(filter $$($1_FILENAME), $$($$($1_BASE)_PRECOMPILED_HEADER_EXCLUDE)), ) - $1_USE_PCH_FLAGS := $$($$($1_BASE)_USE_PCH_FLAGS) + ifneq ($$($2_PRECOMPILED_HEADER), ) + ifeq ($$(filter $$($1_FILENAME), $$($2_PRECOMPILED_HEADER_EXCLUDE)), ) + $1_USE_PCH_FLAGS := $$($2_USE_PCH_FLAGS) endif endif ifneq ($(DISABLE_WARNING_PREFIX), ) $1_WARNINGS_FLAGS := $$(addprefix $(DISABLE_WARNING_PREFIX), \ - $$($$($1_BASE)_DISABLED_WARNINGS_$(TOOLCHAIN_TYPE)_$$($1_FILENAME)) \ - $$($$($1_BASE)_DISABLED_WARNINGS_$(TOOLCHAIN_TYPE)_$(OPENJDK_TARGET_OS)_$$($1_FILENAME))) + $$($2_DISABLED_WARNINGS_$(TOOLCHAIN_TYPE)_$$($1_FILENAME)) \ + $$($2_DISABLED_WARNINGS_$(TOOLCHAIN_TYPE)_$(OPENJDK_TARGET_OS)_$$($1_FILENAME))) endif - $1_BASE_CFLAGS := $$($$($1_BASE)_CFLAGS) $$($$($1_BASE)_EXTRA_CFLAGS) \ - $$($$($1_BASE)_SYSROOT_CFLAGS) - $1_BASE_CXXFLAGS := $$($$($1_BASE)_CXXFLAGS) $$($$($1_BASE)_EXTRA_CXXFLAGS) \ - $$($$($1_BASE)_SYSROOT_CFLAGS) $$($1_EXTRA_CXXFLAGS) - $1_BASE_ASFLAGS := $$($$($1_BASE)_ASFLAGS) $$($$($1_BASE)_EXTRA_ASFLAGS) + $1_BASE_CFLAGS := $$($2_CFLAGS) $$($2_EXTRA_CFLAGS) \ + $$($2_SYSROOT_CFLAGS) + $1_BASE_CXXFLAGS := $$($2_CXXFLAGS) $$($2_EXTRA_CXXFLAGS) \ + $$($2_SYSROOT_CFLAGS) $$($1_EXTRA_CXXFLAGS) + $1_BASE_ASFLAGS := $$($2_ASFLAGS) $$($2_EXTRA_ASFLAGS) endef ################################################################################ @@ -129,6 +131,20 @@ define SetupCompilerFlags $1_EXTRA_CXXFLAGS := $$($1_EXTRA_CFLAGS) endif + $$(call SetIfEmpty, $1_COMPILE_WITH_DEBUG_SYMBOLS, $$(COMPILE_WITH_DEBUG_SYMBOLS)) + + ifeq ($(STATIC_LIBS), true) + # For release builds where debug symbols are configured to be moved to + # separate debuginfo files, disable debug symbols for static libs instead. + # We don't currently support this configuration and we don't want symbol + # information in release builds unless explicitly asked to provide it. + ifeq ($(DEBUG_LEVEL), release) + ifeq ($(COPY_DEBUG_SYMBOLS), true) + $1_COMPILE_WITH_DEBUG_SYMBOLS := false + endif + endif + endif + ifeq ($$($1_COMPILE_WITH_DEBUG_SYMBOLS), true) $1_EXTRA_CFLAGS += $$(CFLAGS_DEBUG_SYMBOLS) $1_EXTRA_CXXFLAGS += $$(CFLAGS_DEBUG_SYMBOLS) diff --git a/make/common/native/Link.gmk b/make/common/native/Link.gmk index ea528c0d9aaad..37291d496a686 100644 --- a/make/common/native/Link.gmk +++ b/make/common/native/Link.gmk @@ -27,35 +27,12 @@ # This file contains functionality related to linking a native binary; # creating either a dynamic library, a static library or an executable. -################################################################################ -# Create exported symbols file for static libraries -################################################################################ - -# get the exported symbols from mapfiles and if there -# is no mapfile, get them from the archive -define GetSymbols - $(RM) $$(@D)/$$(basename $$(@F)).symbols; \ - if [ ! -z $$($1_MAPFILE) -a -e $$($1_MAPFILE) ]; then \ - $(ECHO) "Getting symbols from mapfile $$($1_MAPFILE)"; \ - $(AWK) '/global:/','/local:/' $$($1_MAPFILE) | \ - $(SED) -e 's/#.*//;s/global://;s/local://;s/\;//;s/^[ ]*/_/;/^_$$$$/d' | \ - $(EGREP) -v "JNI_OnLoad|JNI_OnUnload|Agent_OnLoad|Agent_OnUnload|Agent_OnAttach" > \ - $$(@D)/$$(basename $$(@F)).symbols || true; \ - $(NM) $(NMFLAGS) $$($1_TARGET) | $(GREP) " T " | \ - $(EGREP) "JNI_OnLoad|JNI_OnUnload|Agent_OnLoad|Agent_OnUnload|Agent_OnAttach" | \ - $(CUT) -d ' ' -f 3 >> $$(@D)/$$(basename $$(@F)).symbols || true;\ - else \ - $(ECHO) "Getting symbols from nm"; \ - $(NM) $(NMFLAGS) -m $$($1_TARGET) | $(GREP) "__TEXT" | \ - $(EGREP) -v "non-external|private extern|__TEXT,__eh_frame" | \ - $(SED) -e 's/.* //' > $$(@D)/$$(basename $$(@F)).symbols; \ - fi -endef ################################################################################ # GetEntitlementsFile # Find entitlements file for executable when signing on macosx. If no # specialized file is found, returns the default file. +# This macro might be called from custom makefiles. # $1 Executable to find entitlements file for. ENTITLEMENTS_DIR := $(TOPDIR)/make/data/macosxsigning ifeq ($(MACOSX_CODESIGN_MODE), debug) @@ -71,7 +48,7 @@ GetEntitlementsFile = \ ) ################################################################################ -define SetupMapfile +define SetupLinking ifneq ($(DISABLE_MAPFILES), true) $1_REAL_MAPFILE := $$($1_MAPFILE) endif @@ -79,10 +56,7 @@ define SetupMapfile ifneq ($$($1_REAL_MAPFILE), ) $1_EXTRA_LDFLAGS += $(call SET_SHARED_LIBRARY_MAPFILE,$$($1_REAL_MAPFILE)) endif -endef -################################################################################ -define SetupStrip # Unless specifically set, stripping should only happen if symbols are also # being copied. $$(call SetIfEmpty, $1_STRIP_SYMBOLS, $$($1_COPY_DEBUG_SYMBOLS)) @@ -99,39 +73,47 @@ endef ################################################################################ define CreateLinkedResult ifeq ($$($1_TYPE), STATIC_LIBRARY) - # Include partial linking when building the static library with clang on linux. - ifeq ($(call isTargetOs, linux), true) - ifneq ($(findstring $(TOOLCHAIN_TYPE), clang), ) - $1_ENABLE_PARTIAL_LINKING := true - endif - endif + $$(eval $$(call CreateStaticLibrary,$1)) + else + $$(eval $$(call CreateDynamicLibraryOrExecutable,$1)) + endif +endef - $1_VARDEPS := $$($1_AR) $$(ARFLAGS) $$($1_ARFLAGS) $$($1_LIBS) \ - $$($1_EXTRA_LIBS) - ifeq ($$($1_ENABLE_PARTIAL_LINKING), true) - $1_VARDEPS += $$($1_LD) $$($1_SYSROOT_LDFLAGS) +################################################################################ +define CreateStaticLibrary + # Include partial linking when building the static library with clang on linux. + ifeq ($(call isTargetOs, linux), true) + ifneq ($(findstring $(TOOLCHAIN_TYPE), clang), ) + $1_ENABLE_PARTIAL_LINKING := true endif - $1_VARDEPS_FILE := $$(call DependOnVariable, $1_VARDEPS, \ - $$($1_OBJECT_DIR)/$$($1_NOSUFFIX).vardeps) + endif - # Generating a static library, ie object file archive. - ifeq ($(STATIC_BUILD), true) - ifeq ($$($1_USE_MAPFILE_FOR_SYMBOLS), true) - STATIC_MAPFILE_DEP := $$($1_MAPFILE) - endif + $1_VARDEPS := $$($1_AR) $$(ARFLAGS) $$($1_ARFLAGS) $$($1_LIBS) \ + $$($1_EXTRA_LIBS) + ifeq ($$($1_ENABLE_PARTIAL_LINKING), true) + $1_VARDEPS += $$($1_LD) $$($1_SYSROOT_LDFLAGS) + endif + $1_VARDEPS_FILE := $$(call DependOnVariable, $1_VARDEPS, \ + $$($1_OBJECT_DIR)/$$($1_NOSUFFIX).vardeps) + + # Generating a static library, ie object file archive. + ifeq ($(STATIC_BUILD), true) + ifeq ($$($1_USE_MAPFILE_FOR_SYMBOLS), true) + STATIC_MAPFILE_DEP := $$($1_MAPFILE) endif + endif - $1_TARGET_DEPS := $$($1_ALL_OBJS) $$($1_RES) $$($1_VARDEPS_FILE) $$(STATIC_MAPFILE_DEP) + $1_TARGET_DEPS := $$($1_ALL_OBJS) $$($1_RES) $$($1_VARDEPS_FILE) $$(STATIC_MAPFILE_DEP) - $1_AR_OBJ_ARG := $$($1_LD_OBJ_ARG) - # With clang on linux, partial linking is enabled and 'AR' takes the output - # object from the partial linking step. - ifeq ($$($1_ENABLE_PARTIAL_LINKING), true) - $1_TARGET_RELOCATABLE := $$($1_OBJECT_DIR)/$$($1_PREFIX)$$($1_NAME)_relocatable$(OBJ_SUFFIX) - $1_AR_OBJ_ARG := $$($1_TARGET_RELOCATABLE) - endif + $1_AR_OBJ_ARG := $$($1_LD_OBJ_ARG) + # With clang on linux, partial linking is enabled and 'AR' takes the output + # object from the partial linking step. + ifeq ($$($1_ENABLE_PARTIAL_LINKING), true) + $1_TARGET_RELOCATABLE := $$($1_OBJECT_DIR)/$$($1_PREFIX)$$($1_NAME)_relocatable$(OBJ_SUFFIX) + $1_AR_OBJ_ARG := $$($1_TARGET_RELOCATABLE) + endif - $$($1_TARGET): $$($1_TARGET_DEPS) + $$($1_TARGET): $$($1_TARGET_DEPS) ifneq ($$($1_OBJ_FILE_LIST), ) ifeq ($$($1_LINK_OBJS_RELATIVE), true) $$(eval $$(call ListPathsSafely, $1_ALL_OBJS_RELATIVE, $$($1_OBJ_FILE_LIST))) @@ -147,7 +129,7 @@ define CreateLinkedResult $(if $$($1_LINK_OBJS_RELATIVE), $$(CD) $$(OUTPUTDIR) ; ) \ $$($1_LD) $(LDFLAGS_CXX_PARTIAL_LINKING) $$($1_SYSROOT_LDFLAGS) \ $(LD_OUT_OPTION)$$($1_TARGET_RELOCATABLE) \ - $$($1_LD_OBJ_ARG)) + $$($1_LD_OBJ_ARG)) endif $$(call ExecuteWithLog, $$($1_OBJECT_DIR)/$$($1_SAFE_NAME)_link, \ $(if $$($1_LINK_OBJS_RELATIVE), $$(CD) $$(OUTPUTDIR) ; ) \ @@ -157,106 +139,124 @@ define CreateLinkedResult ifeq ($$($1_USE_MAPFILE_FOR_SYMBOLS), true) $(CP) $$($1_MAPFILE) $$(@D)/$$(basename $$(@F)).symbols else - $(GetSymbols) + # get the exported symbols from mapfiles and if there + # is no mapfile, get them from the archive + $(RM) $$(@D)/$$(basename $$(@F)).symbols; \ + if [ ! -z $$($1_MAPFILE) -a -e $$($1_MAPFILE) ]; then \ + $(ECHO) "Getting symbols from mapfile $$($1_MAPFILE)"; \ + $(AWK) '/global:/','/local:/' $$($1_MAPFILE) | \ + $(SED) -e 's/#.*//;s/global://;s/local://;s/\;//;s/^[ ]*/_/;/^_$$$$/d' | \ + $(EGREP) -v "JNI_OnLoad|JNI_OnUnload|Agent_OnLoad|Agent_OnUnload|Agent_OnAttach" > \ + $$(@D)/$$(basename $$(@F)).symbols || true; \ + $(NM) $(NMFLAGS) $$($1_TARGET) | $(GREP) " T " | \ + $(EGREP) "JNI_OnLoad|JNI_OnUnload|Agent_OnLoad|Agent_OnUnload|Agent_OnAttach" | \ + $(CUT) -d ' ' -f 3 >> $$(@D)/$$(basename $$(@F)).symbols || true;\ + else \ + $(ECHO) "Getting symbols from nm"; \ + $(NM) $(NMFLAGS) -m $$($1_TARGET) | $(GREP) "__TEXT" | \ + $(EGREP) -v "non-external|private extern|__TEXT,__eh_frame" | \ + $(SED) -e 's/.* //' > $$(@D)/$$(basename $$(@F)).symbols; \ + fi endif endif - else - # A shared dynamic library or an executable binary has been specified - ifeq ($$($1_TYPE), LIBRARY) - # Generating a dynamic library. - $1_EXTRA_LDFLAGS += $$(call SET_SHARED_LIBRARY_NAME,$$($1_BASENAME)) +endef - # Create loadmap on AIX. Helps in diagnosing some problems. - ifneq ($(COMPILER_BINDCMD_FILE_FLAG), ) - $1_EXTRA_LDFLAGS += $(COMPILER_BINDCMD_FILE_FLAG)$$($1_OBJECT_DIR)/$$($1_NOSUFFIX).loadmap - endif - endif +################################################################################ +define CreateDynamicLibraryOrExecutable + # A shared dynamic library or an executable binary has been specified + ifeq ($$($1_TYPE), LIBRARY) + # Generating a dynamic library. + $1_EXTRA_LDFLAGS += $$(call SET_SHARED_LIBRARY_NAME,$$($1_BASENAME)) - ifeq ($(call isTargetOs, windows), true) - ifeq ($$($1_EMBED_MANIFEST), true) - $1_EXTRA_LDFLAGS += -manifest:embed - endif + # Create loadmap on AIX. Helps in diagnosing some problems. + ifneq ($(COMPILER_BINDCMD_FILE_FLAG), ) + $1_EXTRA_LDFLAGS += $(COMPILER_BINDCMD_FILE_FLAG)$$($1_OBJECT_DIR)/$$($1_NOSUFFIX).loadmap + endif + endif - $1_IMPORT_LIBRARY := $$($1_OBJECT_DIR)/$$($1_NAME).lib - $1_EXTRA_LDFLAGS += "-implib:$$($1_IMPORT_LIBRARY)" - ifeq ($$($1_TYPE), LIBRARY) - # To properly trigger downstream dependants of the import library, just as - # for debug files, we must have a recipe in the rule. To avoid rerunning - # the recipe every time have it touch the target. If an import library - # file is deleted by something external, explicitly delete the target to - # trigger a rebuild of both. - ifneq ($$(wildcard $$($1_IMPORT_LIBRARY)), $$($1_IMPORT_LIBRARY)) - $$(call LogDebug, Deleting $$($1_BASENAME) because import library is missing) - $$(shell $(RM) $$($1_TARGET)) - endif - $$($1_IMPORT_LIBRARY): $$($1_TARGET) - $(TOUCH) $$@ + ifeq ($(call isTargetOs, windows), true) + ifeq ($$($1_EMBED_MANIFEST), true) + $1_EXTRA_LDFLAGS += -manifest:embed + endif - $1 += $$($1_IMPORT_LIBRARY) + $1_IMPORT_LIBRARY := $$($1_OBJECT_DIR)/$$($1_NAME).lib + $1_EXTRA_LDFLAGS += "-implib:$$($1_IMPORT_LIBRARY)" + ifeq ($$($1_TYPE), LIBRARY) + # To properly trigger downstream dependants of the import library, just as + # for debug files, we must have a recipe in the rule. To avoid rerunning + # the recipe every time have it touch the target. If an import library + # file is deleted by something external, explicitly delete the target to + # trigger a rebuild of both. + ifneq ($$(wildcard $$($1_IMPORT_LIBRARY)), $$($1_IMPORT_LIBRARY)) + $$(call LogDebug, Deleting $$($1_BASENAME) because import library is missing) + $$(shell $(RM) $$($1_TARGET)) endif - endif + $$($1_IMPORT_LIBRARY): $$($1_TARGET) + $(TOUCH) $$@ - $1_VARDEPS := $$($1_LD) $$($1_SYSROOT_LDFLAGS) $$($1_LDFLAGS) $$($1_EXTRA_LDFLAGS) \ - $$($1_LIBS) $$($1_EXTRA_LIBS) $$($1_MT) \ - $$($1_CREATE_DEBUGINFO_CMDS) $$($1_MANIFEST_VERSION) \ - $$($1_STRIP_CMD) $$($1_CREATE_DEBUGLINK_CMDS) - $1_VARDEPS_FILE := $$(call DependOnVariable, $1_VARDEPS, \ - $$($1_OBJECT_DIR)/$$($1_NOSUFFIX).vardeps) + $1 += $$($1_IMPORT_LIBRARY) + endif + endif - $1_TARGET_DEPS := $$($1_ALL_OBJS) $$($1_RES) $$($1_MANIFEST) \ - $$($1_REAL_MAPFILE) $$($1_VARDEPS_FILE) + $1_VARDEPS := $$($1_LD) $$($1_SYSROOT_LDFLAGS) $$($1_LDFLAGS) $$($1_EXTRA_LDFLAGS) \ + $$($1_LIBS) $$($1_EXTRA_LIBS) $$($1_MT) \ + $$($1_CREATE_DEBUGINFO_CMDS) $$($1_MANIFEST_VERSION) \ + $$($1_STRIP_CMD) $$($1_CREATE_DEBUGLINK_CMDS) + $1_VARDEPS_FILE := $$(call DependOnVariable, $1_VARDEPS, \ + $$($1_OBJECT_DIR)/$$($1_NOSUFFIX).vardeps) - $$($1_TARGET): $$($1_TARGET_DEPS) - ifneq ($$($1_OBJ_FILE_LIST), ) - ifeq ($$($1_LINK_OBJS_RELATIVE), true) - $$(eval $$(call ListPathsSafely, $1_ALL_OBJS_RELATIVE, $$($1_OBJ_FILE_LIST))) - else - $$(eval $$(call ListPathsSafely, $1_ALL_OBJS, $$($1_OBJ_FILE_LIST))) - endif - endif - # Keep as much as possible on one execution line for best performance - # on Windows - $$(call LogInfo, Linking $$($1_BASENAME)) - $$(call MakeDir, $$($1_OUTPUT_DIR) $$($1_SYMBOLS_DIR)) - ifeq ($(call isTargetOs, windows), true) + $1_TARGET_DEPS := $$($1_ALL_OBJS) $$($1_RES) $$($1_MANIFEST) \ + $$($1_REAL_MAPFILE) $$($1_VARDEPS_FILE) - $$(call ExecuteWithLog, $$($1_OBJECT_DIR)/$$($1_SAFE_NAME)_link, \ - $$($1_LD) $$($1_LDFLAGS) $$($1_EXTRA_LDFLAGS) $$($1_SYSROOT_LDFLAGS) \ - $(LD_OUT_OPTION)$$($1_TARGET) $$($1_LD_OBJ_ARG) $$($1_RES) \ - $$($1_LIBS) $$($1_EXTRA_LIBS)) \ - | $(GREP) -v "^ Creating library .*\.lib and object .*\.exp" || \ - test "$$$$?" = "1" ; \ - $$($1_CREATE_DEBUGINFO_CMDS) - $$($1_STRIP_CMD) - $$($1_CREATE_DEBUGLINK_CMDS) - ifeq ($(call isBuildOsEnv, windows.wsl2), true) - $$(CHMOD) +x $$($1_TARGET) - endif - else - $$(call ExecuteWithLog, $$($1_OBJECT_DIR)/$$($1_SAFE_NAME)_link, \ - $$(if $$($1_LINK_OBJS_RELATIVE), $$(CD) $$(OUTPUTDIR) ; ) \ - $$($1_LD) $$($1_LDFLAGS) $$($1_EXTRA_LDFLAGS) $$($1_SYSROOT_LDFLAGS) \ - $(LD_OUT_OPTION)$$($1_TARGET) $$($1_LD_OBJ_ARG) $$($1_RES) \ - $$($1_LIBS) $$($1_EXTRA_LIBS)) ; \ - $$($1_CREATE_DEBUGINFO_CMDS) - $$($1_STRIP_CMD) - $$($1_CREATE_DEBUGLINK_CMDS) - endif - ifeq ($(call isTargetOs, windows), true) - ifneq ($$($1_MANIFEST), ) - $$($1_MT) -nologo -manifest $$($1_MANIFEST) -identity:"$$($1_NAME).exe, version=$$($1_MANIFEST_VERSION)" -outputresource:$$@;#1 - endif - endif - # On macosx, optionally run codesign on every binary. - # Remove signature explicitly first to avoid warnings if the linker - # added a default adhoc signature. - ifeq ($(MACOSX_CODESIGN_MODE), hardened) - $(CODESIGN) --remove-signature $$@ - $(CODESIGN) -f -s "$(MACOSX_CODESIGN_IDENTITY)" --timestamp --options runtime \ - --entitlements $$(call GetEntitlementsFile, $$@) $$@ - else ifeq ($(MACOSX_CODESIGN_MODE), debug) - $(CODESIGN) --remove-signature $$@ - $(CODESIGN) -f -s - --entitlements $$(call GetEntitlementsFile, $$@) $$@ - endif - endif + $$($1_TARGET): $$($1_TARGET_DEPS) + ifneq ($$($1_OBJ_FILE_LIST), ) + ifeq ($$($1_LINK_OBJS_RELATIVE), true) + $$(eval $$(call ListPathsSafely, $1_ALL_OBJS_RELATIVE, $$($1_OBJ_FILE_LIST))) + else + $$(eval $$(call ListPathsSafely, $1_ALL_OBJS, $$($1_OBJ_FILE_LIST))) + endif + endif + # Keep as much as possible on one execution line for best performance + # on Windows + $$(call LogInfo, Linking $$($1_BASENAME)) + $$(call MakeDir, $$($1_OUTPUT_DIR) $$($1_SYMBOLS_DIR)) + ifeq ($(call isTargetOs, windows), true) + $$(call ExecuteWithLog, $$($1_OBJECT_DIR)/$$($1_SAFE_NAME)_link, \ + $$($1_LD) $$($1_LDFLAGS) $$($1_EXTRA_LDFLAGS) $$($1_SYSROOT_LDFLAGS) \ + $(LD_OUT_OPTION)$$($1_TARGET) $$($1_LD_OBJ_ARG) $$($1_RES) \ + $$($1_LIBS) $$($1_EXTRA_LIBS)) \ + | $(GREP) -v "^ Creating library .*\.lib and object .*\.exp" || \ + test "$$$$?" = "1" ; \ + $$($1_CREATE_DEBUGINFO_CMDS) + $$($1_STRIP_CMD) + $$($1_CREATE_DEBUGLINK_CMDS) + ifeq ($(call isBuildOsEnv, windows.wsl2), true) + $$(CHMOD) +x $$($1_TARGET) + endif + else + $$(call ExecuteWithLog, $$($1_OBJECT_DIR)/$$($1_SAFE_NAME)_link, \ + $$(if $$($1_LINK_OBJS_RELATIVE), $$(CD) $$(OUTPUTDIR) ; ) \ + $$($1_LD) $$($1_LDFLAGS) $$($1_EXTRA_LDFLAGS) $$($1_SYSROOT_LDFLAGS) \ + $(LD_OUT_OPTION)$$($1_TARGET) $$($1_LD_OBJ_ARG) $$($1_RES) \ + $$($1_LIBS) $$($1_EXTRA_LIBS)) ; \ + $$($1_CREATE_DEBUGINFO_CMDS) + $$($1_STRIP_CMD) + $$($1_CREATE_DEBUGLINK_CMDS) + endif + ifeq ($(call isTargetOs, windows), true) + ifneq ($$($1_MANIFEST), ) + $$($1_MT) -nologo -manifest $$($1_MANIFEST) -identity:"$$($1_NAME).exe, version=$$($1_MANIFEST_VERSION)" -outputresource:$$@;#1 + endif + endif + # On macosx, optionally run codesign on every binary. + # Remove signature explicitly first to avoid warnings if the linker + # added a default adhoc signature. + ifeq ($(MACOSX_CODESIGN_MODE), hardened) + $(CODESIGN) --remove-signature $$@ + $(CODESIGN) -f -s "$(MACOSX_CODESIGN_IDENTITY)" --timestamp --options runtime \ + --entitlements $$(call GetEntitlementsFile, $$@) $$@ + else ifeq ($(MACOSX_CODESIGN_MODE), debug) + $(CODESIGN) --remove-signature $$@ + $(CODESIGN) -f -s - --entitlements $$(call GetEntitlementsFile, $$@) $$@ + endif endef diff --git a/make/common/native/Paths.gmk b/make/common/native/Paths.gmk index 8d7c426eb3571..67aa61d86e968 100644 --- a/make/common/native/Paths.gmk +++ b/make/common/native/Paths.gmk @@ -113,7 +113,7 @@ else endif ################################################################################ -define LocateSourceFiles +define SetupSourceFiles $$(foreach d, $$($1_SRC), $$(if $$(wildcard $$d), , \ $$(error SRC specified to SetupNativeCompilation $1 contains missing directory $$d))) @@ -143,10 +143,7 @@ define LocateSourceFiles $1_INCLUDE_FILES_PAT := $$(foreach i, $$($1_SRC), $$(addprefix $$i/, $$($1_INCLUDE_FILES))) $1_SRCS := $$(filter $$($1_INCLUDE_FILES_PAT), $$($1_SRCS)) endif - # There can be only a single bin dir root, no need to foreach over the roots. - $1_BINS := $$(wildcard $$($1_OBJECT_DIR)/*$(OBJ_SUFFIX)) # Now we have a list of all c/c++ files to compile: $$($1_SRCS) - # and we have a list of all existing object files: $$($1_BINS) # Prepend the source/bin path to the filter expressions. Then do the filtering. ifneq ($$($1_INCLUDES), ) @@ -184,12 +181,6 @@ define SetupOutputFiles # Calculate the expected output from compiling the sources $1_EXPECTED_OBJS_FILENAMES := $$(call replace_with_obj_extension, $$(notdir $$($1_SRCS))) $1_EXPECTED_OBJS := $$(addprefix $$($1_OBJECT_DIR)/, $$($1_EXPECTED_OBJS_FILENAMES)) - # Are there too many object files on disk? Perhaps because some source file was removed? - $1_SUPERFLOUS_OBJS := $$(sort $$(filter-out $$($1_EXPECTED_OBJS), $$($1_BINS))) - # Clean out the superfluous object files. - ifneq ($$($1_SUPERFLUOUS_OBJS), ) - $$(shell $(RM) -f $$($1_SUPERFLUOUS_OBJS)) - endif # Sort to remove duplicates and provide a reproducible order on the input files to the linker. $1_ALL_OBJS := $$(sort $$($1_EXPECTED_OBJS) $$($1_EXTRA_OBJECT_FILES)) ifeq ($(STATIC_LIBS), true) @@ -200,6 +191,17 @@ define SetupOutputFiles endif endef +################################################################################ +define RemoveSuperfluousOutputFiles + # Are there too many object files on disk? Perhaps because some source file was removed? + $1_BINS := $$(wildcard $$($1_OBJECT_DIR)/*$(OBJ_SUFFIX)) + $1_SUPERFLOUS_OBJS := $$(sort $$(filter-out $$($1_EXPECTED_OBJS), $$($1_BINS))) + # Clean out the superfluous object files. + ifneq ($$($1_SUPERFLUOUS_OBJS), ) + $$(shell $(RM) -f $$($1_SUPERFLUOUS_OBJS)) + endif +endef + ################################################################################ define SetupObjectFileList $1_LD_OBJ_ARG := $$($1_ALL_OBJS) From c022431a00a1d84594779315dd1159a7cf03142e Mon Sep 17 00:00:00 2001 From: Magnus Ihse Bursie Date: Wed, 21 Feb 2024 14:10:38 +0000 Subject: [PATCH 07/38] 8326412: debuginfo files should not have executable bit set Reviewed-by: erikj --- make/common/native/DebugSymbols.gmk | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/make/common/native/DebugSymbols.gmk b/make/common/native/DebugSymbols.gmk index f526c8d4ee321..9f49f5e1d5292 100644 --- a/make/common/native/DebugSymbols.gmk +++ b/make/common/native/DebugSymbols.gmk @@ -59,7 +59,8 @@ define CreateDebugSymbols # so we can run it after strip is called, since strip can sometimes mangle the # embedded debuglink, which we want to avoid. $1_CREATE_DEBUGINFO_CMDS := \ - $$($1_OBJCOPY) --only-keep-debug $$($1_TARGET) $$($1_DEBUGINFO_FILES) $$(NEWLINE) + $$($1_OBJCOPY) --only-keep-debug $$($1_TARGET) $$($1_DEBUGINFO_FILES) && \ + $$(CHMOD) -x $$($1_DEBUGINFO_FILES) $1_CREATE_DEBUGLINK_CMDS := $(CD) $$($1_SYMBOLS_DIR) && \ $$($1_OBJCOPY) --add-gnu-debuglink=$$($1_DEBUGINFO_FILES) $$($1_TARGET) From 51e2dde018746f419922ae40cd039cd6f27f1b75 Mon Sep 17 00:00:00 2001 From: Robbin Ehn Date: Wed, 21 Feb 2024 14:33:29 +0000 Subject: [PATCH 08/38] 8326235: RISC-V: Size CodeCache for short calls encoding Reviewed-by: fyang, tonyp --- src/hotspot/cpu/riscv/globalDefinitions_riscv.hpp | 3 +++ src/hotspot/share/utilities/globalDefinitions.hpp | 7 +++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/hotspot/cpu/riscv/globalDefinitions_riscv.hpp b/src/hotspot/cpu/riscv/globalDefinitions_riscv.hpp index e368bbdc9141f..68cd51ece5f70 100644 --- a/src/hotspot/cpu/riscv/globalDefinitions_riscv.hpp +++ b/src/hotspot/cpu/riscv/globalDefinitions_riscv.hpp @@ -50,6 +50,9 @@ const bool CCallingConventionRequiresIntsAsLongs = false; #define USE_POINTERS_TO_REGISTER_IMPL_ARRAY +// auipc useable for all cc -> cc calls and jumps +#define CODE_CACHE_SIZE_LIMIT ((2*G)-(2*K)) + // The expected size in bytes of a cache line. #define DEFAULT_CACHE_LINE_SIZE 64 diff --git a/src/hotspot/share/utilities/globalDefinitions.hpp b/src/hotspot/share/utilities/globalDefinitions.hpp index 08eb25828703f..c0f5b71966662 100644 --- a/src/hotspot/share/utilities/globalDefinitions.hpp +++ b/src/hotspot/share/utilities/globalDefinitions.hpp @@ -581,13 +581,16 @@ extern uint64_t OopEncodingHeapMax; // Machine dependent stuff +#include CPU_HEADER(globalDefinitions) + // The maximum size of the code cache. Can be overridden by targets. +#ifndef CODE_CACHE_SIZE_LIMIT #define CODE_CACHE_SIZE_LIMIT (2*G) +#endif + // Allow targets to reduce the default size of the code cache. #define CODE_CACHE_DEFAULT_LIMIT CODE_CACHE_SIZE_LIMIT -#include CPU_HEADER(globalDefinitions) - // To assure the IRIW property on processors that are not multiple copy // atomic, sync instructions must be issued between volatile reads to // assure their ordering, instead of after volatile stores. From f0f4d63fa9c9f487198b2a2b7b410b590e1437bc Mon Sep 17 00:00:00 2001 From: Lance Andersen Date: Wed, 21 Feb 2024 14:51:35 +0000 Subject: [PATCH 09/38] 8326351: Update the Zlib version in open/src/java.base/share/legal/zlib.md to 1.3.1 Reviewed-by: iris, naoto, jpai --- src/java.base/share/legal/zlib.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/java.base/share/legal/zlib.md b/src/java.base/share/legal/zlib.md index d856af6ccd404..fcc5457bf5b60 100644 --- a/src/java.base/share/legal/zlib.md +++ b/src/java.base/share/legal/zlib.md @@ -1,9 +1,9 @@ -## zlib v1.2.13 +## zlib v1.3.1 ### zlib License
 
-Copyright (C) 1995-2022 Jean-loup Gailly and Mark Adler
+Copyright (C) 1995-2024 Jean-loup Gailly and Mark Adler
 
 This software is provided 'as-is', without any express or implied
 warranty.  In no event will the authors be held liable for any damages

From 64f7972a3d0c82ad7047f73f0b57c3d88f62935f Mon Sep 17 00:00:00 2001
From: Naoto Sato 
Date: Wed, 21 Feb 2024 16:53:57 +0000
Subject: [PATCH 10/38] 8326158: Javadoc for java.time.DayOfWeek#minus(long)

Reviewed-by: iris, lancea
---
 src/java.base/share/classes/java/time/DayOfWeek.java | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/java.base/share/classes/java/time/DayOfWeek.java b/src/java.base/share/classes/java/time/DayOfWeek.java
index 43dc3aa9c1b0f..ba3107ef7de3d 100644
--- a/src/java.base/share/classes/java/time/DayOfWeek.java
+++ b/src/java.base/share/classes/java/time/DayOfWeek.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2012, 2024, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -370,7 +370,7 @@ public DayOfWeek plus(long days) {
     /**
      * Returns the day-of-week that is the specified number of days before this one.
      * 

- * The calculation rolls around the start of the year from Monday to Sunday. + * The calculation rolls around the start of the week from Monday to Sunday. * The specified period may be negative. *

* This instance is immutable and unaffected by this method call. From 0bcece995840777db660811e4b20bb018e90439b Mon Sep 17 00:00:00 2001 From: Magnus Ihse Bursie Date: Wed, 21 Feb 2024 22:34:58 +0000 Subject: [PATCH 11/38] 8325342: Remove unneeded exceptions in compare.sh Reviewed-by: erikj --- make/scripts/compare.sh | 337 +++++++++++++----------- make/scripts/compare_exceptions.sh.incl | 65 ----- 2 files changed, 186 insertions(+), 216 deletions(-) diff --git a/make/scripts/compare.sh b/make/scripts/compare.sh index 395a0c7bdf8f2..44c700c48957e 100644 --- a/make/scripts/compare.sh +++ b/make/scripts/compare.sh @@ -60,13 +60,15 @@ else STAT_PRINT_SIZE="-c %s" fi -COMPARE_EXCEPTIONS_INCLUDE="$TOPDIR/make/scripts/compare_exceptions.sh.incl" -if [ ! -e "$COMPARE_EXCEPTIONS_INCLUDE" ]; then - echo "Error: Cannot locate the exceptions file, it should have been here: $COMPARE_EXCEPTIONS_INCLUDE" - exit 1 + +if [ "$OPENJDK_TARGET_OS" = "windows" ]; then + # We ship a pdb file inside a published zip. Such files can never be built + # reproducibly, so ignore it. + ACCEPTED_JARZIP_CONTENTS="/modules_libs/java.security.jgss/w2k_lsa_auth.dll.pdb" +elif [ "$OPENJDK_TARGET_OS" = "macosx" ]; then + # Due to signing, we can never get a byte-by-byte identical build on macOS + STRIP_TESTS_BEFORE_COMPARE="true" fi -# Include exception definitions -. "$COMPARE_EXCEPTIONS_INCLUDE" ################################################################################ # @@ -117,35 +119,6 @@ diff_text() { TMP=$($DIFF $THIS_FILE $OTHER_FILE) - if test "x$SUFFIX" = "xclass"; then - if [ "$NAME" = "SystemModules\$all.class" ] \ - || [ "$NAME" = "SystemModules\$default.class" ]; then - # The SystemModules\$*.classes are not comparable as they contain the - # module hashes which would require a whole other level of - # reproducible builds to get reproducible. There is also random - # order of map initialization. - TMP="" - elif [ "$NAME" = "module-info.class" ]; then - # The module-info.class have several issues with random ordering of - # elements in HashSets. - MODULES_CLASS_FILTER="$SED \ - -e 's/,$//' \ - -e 's/;$//' \ - -e 's/^ *[0-9]*://' \ - -e 's/#[0-9]* */#/' \ - -e 's/ *\/\// \/\//' \ - -e 's/aload *[0-9]*/aload X/' \ - -e 's/ldc_w/ldc /' \ - | $SORT \ - " - $JAVAP -c -constants -l -p "${OTHER_FILE}" \ - | eval "$MODULES_CLASS_FILTER" > ${OTHER_FILE}.javap & - $JAVAP -c -constants -l -p "${THIS_FILE}" \ - | eval "$MODULES_CLASS_FILTER" > ${THIS_FILE}.javap & - wait - TMP=$($DIFF ${OTHER_FILE}.javap ${THIS_FILE}.javap) - fi - fi if test -n "$TMP"; then echo Files $OTHER_FILE and $THIS_FILE differ @@ -312,75 +285,60 @@ compare_file_types() { } ################################################################################ -# Compare the rest of the files +# Find all files to compare and separate them into different categories -compare_general_files() { +locate_files() { THIS_DIR=$1 - OTHER_DIR=$2 - WORK_DIR=$3 + TEMP_DIR=$COMPARE_ROOT/support + $MKDIR -p $TEMP_DIR - GENERAL_FILES=$(cd $THIS_DIR && $FIND . -type f ! -name "*.so" ! -name "*.jar" \ - ! -name "*.zip" ! -name "*.debuginfo" ! -name "*.dylib" ! -name "jexec" \ - ! -name "modules" ! -name "ct.sym" ! -name "*.diz" ! -name "*.dll" \ - ! -name "*.cpl" ! -name "*.pdb" ! -name "*.exp" ! -name "*.ilk" \ - ! -name "*.lib" ! -name "*.jmod" ! -name "*.exe" \ - ! -name "*.obj" ! -name "*.o" ! -name "jspawnhelper" ! -name "*.a" \ - ! -name "*.tar.gz" ! -name "gtestLauncher" \ - ! -name "*.map" \ - | $GREP -v "./bin/" | $SORT | $FILTER) + ALL_FILES_PATH=$TEMP_DIR/all_files.txt + cd $THIS_DIR && $FIND . -type f | $SORT | $FILTER > $ALL_FILES_PATH - echo Other files with binary differences... - for f in $GENERAL_FILES - do - # Skip all files in test/*/native - if [[ "$f" == */native/* ]]; then - continue - fi - if [ -e $OTHER_DIR/$f ]; then - SUFFIX="${f##*.}" - if [ "$(basename $f)" = "release" ]; then - # In release file, ignore differences in source rev numbers - OTHER_FILE=$WORK_DIR/$f.other - THIS_FILE=$WORK_DIR/$f.this - $MKDIR -p $(dirname $OTHER_FILE) - $MKDIR -p $(dirname $THIS_FILE) - RELEASE_FILTER="$SED -e 's/SOURCE=".*"/SOURCE=/g'" - $CAT $OTHER_DIR/$f | eval "$RELEASE_FILTER" > $OTHER_FILE - $CAT $THIS_DIR/$f | eval "$RELEASE_FILTER" > $THIS_FILE - elif [ "$SUFFIX" = "svg" ]; then - # GraphViz has non-determinism when generating svg files - OTHER_FILE=$WORK_DIR/$f.other - THIS_FILE=$WORK_DIR/$f.this - $MKDIR -p $(dirname $OTHER_FILE) $(dirname $THIS_FILE) - SVG_FILTER="$SED \ - -e 's/edge[0-9][0-9]*/edgeX/g' - " - $CAT $OTHER_DIR/$f | eval "$SVG_FILTER" > $OTHER_FILE - $CAT $THIS_DIR/$f | eval "$SVG_FILTER" > $THIS_FILE - elif [ "$SUFFIX" = "jar_contents" ]; then - # The jar_contents files may have some lines in random order - OTHER_FILE=$WORK_DIR/$f.other - THIS_FILE=$WORK_DIR/$f.this - $MKDIR -p $(dirname $OTHER_FILE) $(dirname $THIS_FILE) - $RM $OTHER_FILE $THIS_FILE - $CAT $OTHER_DIR/$f | $SORT > $OTHER_FILE - $CAT $THIS_DIR/$f | $SORT > $THIS_FILE - else - OTHER_FILE=$OTHER_DIR/$f - THIS_FILE=$THIS_DIR/$f - fi - DIFF_OUT=$($DIFF $OTHER_FILE $THIS_FILE 2>&1) - if [ -n "$DIFF_OUT" ]; then - echo $f - REGRESSIONS=true - if [ "$SHOW_DIFFS" = "true" ]; then - echo "$DIFF_OUT" - fi - fi - fi - done + ZIP_FILES_PATH=$TEMP_DIR/zip_files.txt + ZIP_FILTER="-e '\.zip$' -e '\.tar.gz$'" + $CAT "$ALL_FILES_PATH" | eval $GREP $ZIP_FILTER > $ZIP_FILES_PATH + + JMOD_FILES_PATH=$TEMP_DIR/jmod_files.txt + JMOD_FILTER="-e '\.jmod$'" + $CAT "$ALL_FILES_PATH" | eval $GREP $JMOD_FILTER > $JMOD_FILES_PATH + + JAR_FILES_PATH=$TEMP_DIR/jar_files.txt + JAR_FILTER="-e '\.jar$' -e '\.war$' -e '/module$'" + $CAT "$ALL_FILES_PATH" | eval $GREP $JAR_FILTER > $JAR_FILES_PATH + + LIB_FILES_PATH=$TEMP_DIR/lib_files.txt + LIB_FILTER="-e '\.dylib$' -e '/lib.*\.so$' -e '\.dll$' -e '\.obj$' -e '\.o$' -e '\.a$' -e '\.cpl$'" + # On macos, filter out the dSYM debug symbols files. They are identically named .dylib files that reside + # under a *.dSYM directory + LIB_EXCLUDE="-e '/lib.*\.dSYM/'" + $CAT "$ALL_FILES_PATH" | eval $GREP $LIB_FILTER | eval $GREP -v $LIB_EXCLUDE > $LIB_FILES_PATH + DEBUG_FILES_PATH=$TEMP_DIR/debug_files.txt + DEBUG_FILTER="-e '\.dSYM/' -e '\.debuginfo$' -e '\.diz$' -e '\.pdb$' -e '\.map$'" + $CAT "$ALL_FILES_PATH" | eval $GREP $DEBUG_FILTER > $DEBUG_FILES_PATH + EXEC_FILES_PATH=$TEMP_DIR/exec_files.txt + if [ "$OPENJDK_TARGET_OS" = "windows" ]; then + EXEC_FILTER="-e '\.exe$'" + $CAT "$ALL_FILES_PATH" | eval $GREP $EXEC_FILTER > $EXEC_FILES_PATH + else + # Find all files with the executable bit set + cd $THIS_DIR && $FIND . -type f -perm -100 | $SORT | $FILTER > $EXEC_FILES_PATH + fi + + OTHER_FILES_PATH=$TEMP_DIR/other_files.txt + ACCOUNTED_FILES_PATH=$TEMP_DIR/accounted_files.txt + $CAT $ZIP_FILES_PATH $JMOD_FILES_PATH $JAR_FILES_PATH $LIB_FILES_PATH $DEBUG_FILES_PATH $EXEC_FILES_PATH > $ACCOUNTED_FILES_PATH + $CAT $ACCOUNTED_FILES_PATH $ALL_FILES_PATH | $SORT | $UNIQ -u > $OTHER_FILES_PATH + + ALL_ZIP_FILES=$($CAT $ZIP_FILES_PATH) + ALL_JMOD_FILES=$($CAT $JMOD_FILES_PATH) + ALL_JAR_FILES=$($CAT $JAR_FILES_PATH) + ALL_LIB_FILES=$($CAT $LIB_FILES_PATH) + ALL_DEBUG_FILES=$($CAT $DEBUG_FILES_PATH) + ALL_EXEC_FILES=$($CAT $EXEC_FILES_PATH) + ALL_OTHER_FILES=$($CAT $OTHER_FILES_PATH) } ################################################################################ @@ -450,12 +408,14 @@ compare_zip_file() { if [ -n "$ONLY_OTHER" ]; then echo " Only OTHER $ZIP_FILE contains:" echo "$ONLY_OTHER" | sed "s|Only in $OTHER_UNZIPDIR| |"g | sed 's|: |/|g' + REGRESSIONS=true return_value=1 fi if [ -n "$ONLY_THIS" ]; then echo " Only THIS $ZIP_FILE contains:" echo "$ONLY_THIS" | sed "s|Only in $THIS_UNZIPDIR| |"g | sed 's|: |/|g' + REGRESSIONS=true return_value=1 fi @@ -484,6 +444,7 @@ compare_zip_file() { done if [ -s "$WORK_DIR/$ZIP_FILE.diffs" ]; then + REGRESSIONS=true return_value=1 echo " Differing files in $ZIP_FILE" $CAT $WORK_DIR/$ZIP_FILE.diffs | $GREP 'differ$' | cut -f 2 -d ' ' | \ @@ -508,6 +469,7 @@ compare_zip_file() { compare_bin_file $THIS_UNZIPDIR $OTHER_UNZIPDIR $WORK_DIR/$ZIP_FILE.bin \ $file if [ "$?" != "0" ]; then + REGRESSIONS=true return_value=1 fi done @@ -547,12 +509,14 @@ compare_jmod_file() { if [ -n "$ONLY_OTHER" ]; then echo " Only OTHER $JMOD_FILE contains:" echo "$ONLY_OTHER" | sed "s|^>| |"g | sed 's|: |/|g' + REGRESSIONS=true return_value=1 fi if [ -n "$ONLY_THIS" ]; then echo " Only THIS $JMOD_FILE contains:" echo "$ONLY_THIS" | sed "s|^<| |"g | sed 's|: |/|g' + REGRESSIONS=true return_value=1 fi @@ -567,19 +531,18 @@ compare_all_zip_files() { OTHER_DIR=$2 WORK_DIR=$3 - ZIPS=$(cd $THIS_DIR && $FIND . -type f -name "*.zip" -o -name "*.tar.gz" \ - | $SORT | $FILTER ) + locate_files $THIS_DIR - if [ -n "$ZIPS" ]; then + if [ -n "$ALL_ZIP_FILES" ]; then echo Zip/tar.gz files... return_value=0 - for f in $ZIPS; do + for f in $ALL_ZIP_FILES; do if [ -f "$OTHER_DIR/$f" ]; then compare_zip_file $THIS_DIR $OTHER_DIR $WORK_DIR $f if [ "$?" != "0" ]; then - return_value=1 REGRESSIONS=true + return_value=1 fi fi done @@ -596,18 +559,18 @@ compare_all_jmod_files() { OTHER_DIR=$2 WORK_DIR=$3 - JMODS=$(cd $THIS_DIR && $FIND . -type f -name "*.jmod" | $SORT | $FILTER ) + locate_files $THIS_DIR - if [ -n "$JMODS" ]; then + if [ -n "$ALL_JMOD_FILES" ]; then echo Jmod files... return_value=0 - for f in $JMODS; do + for f in $ALL_JMOD_FILES; do if [ -f "$OTHER_DIR/$f" ]; then compare_jmod_file $THIS_DIR $OTHER_DIR $WORK_DIR $f if [ "$?" != "0" ]; then - return_value=1 REGRESSIONS=true + return_value=1 fi fi done @@ -624,20 +587,18 @@ compare_all_jar_files() { OTHER_DIR=$2 WORK_DIR=$3 - # TODO filter? - ZIPS=$(cd $THIS_DIR && $FIND . -type f -name "*.jar" -o -name "*.war" \ - -o -name "modules" | $SORT | $FILTER) + locate_files $THIS_DIR - if [ -n "$ZIPS" ]; then + if [ -n "$ALL_JAR_FILES" ]; then echo Jar files... return_value=0 - for f in $ZIPS; do + for f in $ALL_JAR_FILES; do if [ -f "$OTHER_DIR/$f" ]; then compare_zip_file $THIS_DIR $OTHER_DIR $WORK_DIR $f if [ "$?" != "0" ]; then - return_value=1 REGRESSIONS=true + return_value=1 fi fi done @@ -1049,23 +1010,16 @@ compare_all_libs() { OTHER_DIR=$2 WORK_DIR=$3 - LIBS=$(cd $THIS_DIR && $FIND . -type f \( -name 'lib*.so' -o -name '*.dylib' \ - -o -name '*.dll' -o -name '*.obj' -o -name '*.o' -o -name '*.a' \ - -o -name '*.cpl' \) | $SORT | $FILTER) + locate_files $THIS_DIR - # On macos, filter out the dSYM debug symbols files as they are also - # named *.dylib. - if [ "$OPENJDK_TARGET_OS" = "macosx" ]; then - LIBS=$(echo "$LIBS" | $GREP -v '\.dSYM/') - fi - - if [ -n "$LIBS" ]; then + if [ -n "$ALL_LIB_FILES" ]; then echo Libraries... print_binary_diff_header - for l in $LIBS; do + for l in $ALL_LIB_FILES; do if [ -f "$OTHER_DIR/$l" ]; then compare_bin_file $THIS_DIR $OTHER_DIR $WORK_DIR $l if [ "$?" != "0" ]; then + REGRESSIONS=true return_value=1 fi fi @@ -1083,33 +1037,16 @@ compare_all_execs() { OTHER_DIR=$2 WORK_DIR=$3 - if [ "$OPENJDK_TARGET_OS" = "windows" ]; then - EXECS=$(cd $THIS_DIR && $FIND . -type f -name '*.exe' | $SORT | $FILTER) - else - EXECS=$(cd $THIS_DIR && $FIND . -name db -prune -o -type f -perm -100 \! \ - \( -name '*.so' -o -name '*.dylib' -o -name '*.dll' -o -name '*.cgi' \ - -o -name '*.jar' -o -name '*.diz' -o -name 'jcontrol' -o -name '*.properties' \ - -o -name '*.data' -o -name '*.bfc' -o -name '*.src' -o -name '*.txt' \ - -o -name '*.cfg' -o -name 'meta-index' -o -name '*.properties.ja' \ - -o -name '*.xml' -o -name '*.html' -o -name '*.png' -o -name 'README' \ - -o -name '*.zip' -o -name '*.jimage' -o -name '*.java' -o -name '*.mf' \ - -o -name '*.jpg' -o -name '*.wsdl' -o -name '*.js' -o -name '*.sh' \ - -o -name '*.bat' -o -name '*LICENSE' -o -name '*.d' -o -name '*store' \ - -o -name 'blocked' -o -name '*certs' -o -name '*.ttf' \ - -o -name '*.jfc' -o -name '*.dat' -o -name 'release' -o -name '*.dir'\ - -o -name '*.sym' -o -name '*.idl' -o -name '*.h' -o -name '*.access' \ - -o -name '*.template' -o -name '*.policy' -o -name '*.security' \ - -o -name 'COPYRIGHT' -o -name '*.1' -o -name '*.debuginfo' \ - -o -name 'classlist' \) | $SORT | $FILTER) - fi - - if [ -n "$EXECS" ]; then + locate_files $THIS_DIR + + if [ -n "$ALL_EXEC_FILES" ]; then echo Executables... print_binary_diff_header - for e in $EXECS; do + for e in $ALL_EXEC_FILES; do if [ -f "$OTHER_DIR/$e" ]; then compare_bin_file $THIS_DIR $OTHER_DIR $WORK_DIR $e if [ "$?" != "0" ]; then + REGRESSIONS=true return_value=1 fi fi @@ -1119,6 +1056,95 @@ compare_all_execs() { return $return_value } +################################################################################ +# Compare native debug symbol files + +compare_all_debug_files() { + THIS_DIR=$1 + OTHER_DIR=$2 + WORK_DIR=$3 + + locate_files $THIS_DIR + + echo Debug symbol files with binary differences... + for f in $ALL_DEBUG_FILES + do + if [ -e $OTHER_DIR/$f ]; then + SUFFIX="${f##*.}" + if [ "$SUFFIX" = "pdb" ]; then + # pdb files are never reproducible + DIFF_OUT="" + else + OTHER_FILE=$OTHER_DIR/$f + THIS_FILE=$THIS_DIR/$f + DIFF_OUT=$($DIFF $OTHER_FILE $THIS_FILE 2>&1) + fi + + if [ -n "$DIFF_OUT" ]; then + echo $f + REGRESSIONS=true + if [ "$SHOW_DIFFS" = "true" ]; then + echo "$DIFF_OUT" + fi + fi + fi + done +} + +################################################################################ +# Compare the rest of the files + +compare_all_other_files() { + THIS_DIR=$1 + OTHER_DIR=$2 + WORK_DIR=$3 + + locate_files $THIS_DIR + + echo Other files with binary differences... + for f in $ALL_OTHER_FILES + do + # Skip all files in test/*/native + if [[ "$f" == */native/* ]]; then + continue + fi + if [ -e $OTHER_DIR/$f ]; then + SUFFIX="${f##*.}" + if [ "$(basename $f)" = "release" ]; then + # In release file, ignore differences in source rev numbers + OTHER_FILE=$WORK_DIR/$f.other + THIS_FILE=$WORK_DIR/$f.this + $MKDIR -p $(dirname $OTHER_FILE) + $MKDIR -p $(dirname $THIS_FILE) + RELEASE_FILTER="$SED -e 's/SOURCE=".*"/SOURCE=/g'" + $CAT $OTHER_DIR/$f | eval "$RELEASE_FILTER" > $OTHER_FILE + $CAT $THIS_DIR/$f | eval "$RELEASE_FILTER" > $THIS_FILE + elif [ "$SUFFIX" = "jar_contents" ]; then + # The jar_contents files are generated by the build and may have + # some lines in random order. They are only included for demos, + # which they shouldn't really... + OTHER_FILE=$WORK_DIR/$f.other + THIS_FILE=$WORK_DIR/$f.this + $MKDIR -p $(dirname $OTHER_FILE) $(dirname $THIS_FILE) + $RM $OTHER_FILE $THIS_FILE + $CAT $OTHER_DIR/$f | $SORT > $OTHER_FILE + $CAT $THIS_DIR/$f | $SORT > $THIS_FILE + else + OTHER_FILE=$OTHER_DIR/$f + THIS_FILE=$THIS_DIR/$f + fi + DIFF_OUT=$($DIFF $OTHER_FILE $THIS_FILE 2>&1) + if [ -n "$DIFF_OUT" ]; then + echo $f + REGRESSIONS=true + if [ "$SHOW_DIFFS" = "true" ]; then + echo "$DIFF_OUT" + fi + fi + fi + done +} + ################################################################################ # Initiate configuration @@ -1517,22 +1543,31 @@ fi if [ "$CMP_GENERAL" = "true" ]; then if [ -n "$THIS_JDK" ] && [ -n "$OTHER_JDK" ]; then echo -n "JDK " - compare_general_files $THIS_JDK $OTHER_JDK $COMPARE_ROOT/jdk + compare_all_other_files $THIS_JDK $OTHER_JDK $COMPARE_ROOT/jdk + echo -n "JDK " + compare_all_debug_files $THIS_JDK $OTHER_JDK $COMPARE_ROOT/jdk fi if [ -n "$THIS_JDK_BUNDLE" ] && [ -n "$OTHER_JDK_BUNDLE" ]; then echo -n "JDK Bundle " - compare_general_files $THIS_JDK_BUNDLE $OTHER_JDK_BUNDLE $COMPARE_ROOT/jdk-bundle + compare_all_other_files $THIS_JDK_BUNDLE $OTHER_JDK_BUNDLE $COMPARE_ROOT/jdk-bundle + echo -n "JDK Bundle " + compare_all_debug_files $THIS_JDK_BUNDLE $OTHER_JDK_BUNDLE $COMPARE_ROOT/jdk-bundle fi if [ -n "$THIS_DOCS" ] && [ -n "$OTHER_DOCS" ]; then echo -n "Docs " - compare_general_files $THIS_DOCS $OTHER_DOCS $COMPARE_ROOT/docs + compare_all_other_files $THIS_DOCS $OTHER_DOCS $COMPARE_ROOT/docs + echo -n "Docs " + compare_all_debug_files $THIS_DOCS $OTHER_DOCS $COMPARE_ROOT/docs fi if [ -n "$THIS_TEST" ] && [ -n "$OTHER_TEST" ]; then echo -n "Test " - compare_general_files $THIS_TEST $OTHER_TEST $COMPARE_ROOT/test + compare_all_other_files $THIS_TEST $OTHER_TEST $COMPARE_ROOT/test + echo -n "Test " + compare_all_debug_files $THIS_TEST $OTHER_TEST $COMPARE_ROOT/test fi if [ -n "$THIS_BASE_DIR" ] && [ -n "$OTHER_BASE_DIR" ]; then - compare_general_files $THIS_BASE_DIR $OTHER_BASE_DIR $COMPARE_ROOT/base_dir + compare_all_other_files $THIS_BASE_DIR $OTHER_BASE_DIR $COMPARE_ROOT/base_dir + compare_all_debug_files $THIS_BASE_DIR $OTHER_BASE_DIR $COMPARE_ROOT/base_dir fi fi diff --git a/make/scripts/compare_exceptions.sh.incl b/make/scripts/compare_exceptions.sh.incl index cfbfeeb5be4f9..e69de29bb2d1d 100644 --- a/make/scripts/compare_exceptions.sh.incl +++ b/make/scripts/compare_exceptions.sh.incl @@ -1,65 +0,0 @@ -#!/bin/bash -# -# Copyright (c) 2012, 2023, Oracle and/or its affiliates. All rights reserved. -# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. -# -# This code is free software; you can redistribute it and/or modify it -# under the terms of the GNU General Public License version 2 only, as -# published by the Free Software Foundation. -# -# This code is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License -# version 2 for more details (a copy is included in the LICENSE file that -# accompanied this code). -# -# You should have received a copy of the GNU General Public License version -# 2 along with this work; if not, write to the Free Software Foundation, -# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. -# -# Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA -# or visit www.oracle.com if you need additional information or have any -# questions. -# - -# This script is not to be run as stand-alone, it should be included from -# compare.sh. - -########################################################################################## -# Check that we are run via inclusion from compare.sh and not as stand-alone. -if [ -z "$COMPARE_EXCEPTIONS_INCLUDE" ]; then - echo "Error: This script should not be run as stand-alone. It is included by compare.sh" - exit 1 -fi - -########################################################################################## -# Diff exceptions - -if [ "$OPENJDK_TARGET_OS" = "linux" ]; then - if [ "$USE_PRECOMPILED_HEADER" = "true" ]; then - ACCEPTED_BIN_DIFF=" - ./lib/server/libjvm.so - ./hotspot/gtest/server/libjvm.so - " - STRIP_BEFORE_COMPARE=" - ./hotspot/gtest/server/libjvm.so - " - fi -elif [ "$OPENJDK_TARGET_OS" = "windows" ]; then - SKIP_BIN_DIFF="true" - SKIP_FULLDUMP_DIFF="true" - ACCEPTED_JARZIP_CONTENTS=" - /modules_libs/java.security.jgss/w2k_lsa_auth.dll.pdb - /modules_libs/java.security.jgss/w2k_lsa_auth.dll.map - /modules_libs/java.security.jgss/w2k_lsa_auth.dll - " -elif [ "$OPENJDK_TARGET_OS" = "macosx" ]; then - ACCEPTED_BIN_DIFF=" - ./lib/libawt_lwawt.dylib - ./lib/libosxapp.dylib - ./lib/libosxui.dylib - ./lib/server/libjvm.dylib - ./hotspot/gtest/server/libjvm.dylib - " - STRIP_TESTS_BEFORE_COMPARE="true" -fi From 8e5f6ddb68572c0cc8b6e256e423706f6f7cec94 Mon Sep 17 00:00:00 2001 From: Sam James Date: Thu, 22 Feb 2024 06:27:25 +0000 Subject: [PATCH 12/38] 8324243: Compilation failures in java.desktop module with gcc 14 Reviewed-by: jwaters, ihse, kbarrett, prr --- make/modules/java.desktop/lib/Awt2dLibraries.gmk | 4 +++- .../linux/native/libjsound/PLATFORM_API_LinuxOS_ALSA_MidiIn.c | 2 +- .../native/libjsound/PLATFORM_API_LinuxOS_ALSA_MidiUtils.c | 2 +- src/java.desktop/share/native/libfontmanager/sunFont.c | 2 +- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/make/modules/java.desktop/lib/Awt2dLibraries.gmk b/make/modules/java.desktop/lib/Awt2dLibraries.gmk index e274005e60741..6fc9ed2fb2bfe 100644 --- a/make/modules/java.desktop/lib/Awt2dLibraries.gmk +++ b/make/modules/java.desktop/lib/Awt2dLibraries.gmk @@ -506,8 +506,10 @@ else # noexcept-type required for GCC 7 builds. Not required for GCC 8+. # expansion-to-defined required for GCC 9 builds. Not required for GCC 10+. # maybe-uninitialized required for GCC 8 builds. Not required for GCC 9+. + # calloc-transposed-args required for GCC 14 builds. (fixed upstream in Harfbuzz 032c931e1c0cfb20f18e5acb8ba005775242bd92) HARFBUZZ_DISABLED_WARNINGS_CXX_gcc := class-memaccess noexcept-type \ - expansion-to-defined dangling-reference maybe-uninitialized + expansion-to-defined dangling-reference maybe-uninitialized \ + calloc-transposed-args HARFBUZZ_DISABLED_WARNINGS_clang := missing-field-initializers range-loop-analysis HARFBUZZ_DISABLED_WARNINGS_microsoft := 4267 4244 diff --git a/src/java.desktop/linux/native/libjsound/PLATFORM_API_LinuxOS_ALSA_MidiIn.c b/src/java.desktop/linux/native/libjsound/PLATFORM_API_LinuxOS_ALSA_MidiIn.c index 6ff6fc681e27c..d7a3f1115b5d1 100644 --- a/src/java.desktop/linux/native/libjsound/PLATFORM_API_LinuxOS_ALSA_MidiIn.c +++ b/src/java.desktop/linux/native/libjsound/PLATFORM_API_LinuxOS_ALSA_MidiIn.c @@ -218,7 +218,7 @@ MidiMessage* MIDI_IN_GetMessage(MidiDeviceHandle* handle) { return NULL; } } - jdk_message = (MidiMessage*) calloc(sizeof(MidiMessage), 1); + jdk_message = (MidiMessage*) calloc(1, sizeof(MidiMessage)); if (!jdk_message) { ERROR0("< ERROR: MIDI_IN_GetMessage(): out of memory\n"); return NULL; diff --git a/src/java.desktop/linux/native/libjsound/PLATFORM_API_LinuxOS_ALSA_MidiUtils.c b/src/java.desktop/linux/native/libjsound/PLATFORM_API_LinuxOS_ALSA_MidiUtils.c index d528e4869ac6d..96193b5f734ea 100644 --- a/src/java.desktop/linux/native/libjsound/PLATFORM_API_LinuxOS_ALSA_MidiUtils.c +++ b/src/java.desktop/linux/native/libjsound/PLATFORM_API_LinuxOS_ALSA_MidiUtils.c @@ -383,7 +383,7 @@ INT32 openMidiDevice(snd_rawmidi_stream_t direction, INT32 deviceIndex, TRACE0("> openMidiDevice()\n"); - (*handle) = (MidiDeviceHandle*) calloc(sizeof(MidiDeviceHandle), 1); + (*handle) = (MidiDeviceHandle*) calloc(1, sizeof(MidiDeviceHandle)); if (!(*handle)) { ERROR0("ERROR: openDevice: out of memory\n"); return MIDI_OUT_OF_MEMORY; diff --git a/src/java.desktop/share/native/libfontmanager/sunFont.c b/src/java.desktop/share/native/libfontmanager/sunFont.c index 0461d61c26cf4..b50a67e5c40b0 100644 --- a/src/java.desktop/share/native/libfontmanager/sunFont.c +++ b/src/java.desktop/share/native/libfontmanager/sunFont.c @@ -67,7 +67,7 @@ int isNullScalerContext(void *context) { */ JNIEXPORT jlong JNICALL Java_sun_font_NullFontScaler_getGlyphImage (JNIEnv *env, jobject scaler, jlong pContext, jint glyphCode) { - void *nullscaler = calloc(sizeof(GlyphInfo), 1); + void *nullscaler = calloc(1, sizeof(GlyphInfo)); return ptr_to_jlong(nullscaler); } From 8b3050338768ea7f378fbc39dedb51be9846137d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Laurent=20Bourg=C3=A8s?= Date: Thu, 22 Feb 2024 07:57:21 +0000 Subject: [PATCH 13/38] 8323695: RenderPerf (2D) enhancements (23.12) Reviewed-by: avu, prr --- .../src/renderperf/RenderPerfTest.java | 2137 ++++++++++++++--- 1 file changed, 1776 insertions(+), 361 deletions(-) diff --git a/test/jdk/performance/client/RenderPerfTest/src/renderperf/RenderPerfTest.java b/test/jdk/performance/client/RenderPerfTest/src/renderperf/RenderPerfTest.java index 51beaba5172e1..15a5cdf5665d8 100644 --- a/test/jdk/performance/client/RenderPerfTest/src/renderperf/RenderPerfTest.java +++ b/test/jdk/performance/client/RenderPerfTest/src/renderperf/RenderPerfTest.java @@ -1,6 +1,6 @@ /* - * Copyright (c) 2019, 2021, Oracle and/or its affiliates. All rights reserved. - * Copyright (c) 2022, JetBrains s.r.o.. All rights reserved. + * Copyright (c) 2019, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 2024, JetBrains s.r.o.. All rights reserved. * 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,20 +24,33 @@ package renderperf; +import java.awt.AWTException; import java.awt.AlphaComposite; import java.awt.Color; +import java.awt.Composite; import java.awt.Dimension; import java.awt.Font; import java.awt.Graphics; import java.awt.Graphics2D; +import java.awt.GraphicsConfiguration; +import java.awt.GraphicsDevice; +import java.awt.GraphicsEnvironment; import java.awt.Image; +import java.awt.Insets; import java.awt.LinearGradientPaint; +import java.awt.Point; import java.awt.RadialGradientPaint; +import java.awt.Rectangle; import java.awt.RenderingHints; import java.awt.Robot; +import java.awt.Toolkit; +import java.awt.Transparency; +import java.awt.event.ComponentAdapter; +import java.awt.event.ComponentEvent; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; + import java.awt.geom.AffineTransform; import java.awt.geom.Ellipse2D; import java.awt.geom.Point2D; @@ -48,67 +61,206 @@ import java.awt.image.DataBufferByte; import java.awt.image.DataBufferInt; import java.awt.image.DataBufferShort; +import java.awt.image.VolatileImage; +import java.io.File; +import java.io.FileOutputStream; import java.io.IOException; -import java.lang.reflect.InvocationTargetException; +import java.io.PrintWriter; +import java.nio.charset.Charset; + import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.HashMap; import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Locale; +import java.util.Map; import java.util.Objects; -import java.util.concurrent.CountDownLatch; +import java.util.Set; +import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; +import java.util.function.IntBinaryOperator; +import java.util.regex.Pattern; + import javax.imageio.ImageIO; import javax.swing.JFrame; +import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.SwingUtilities; import javax.swing.WindowConstants; -public class RenderPerfTest { - private static HashSet ignoredTests = new HashSet<>(); + +public final class RenderPerfTest { + + private final static String VERSION = "RenderPerfTest 2024.02"; + + private static final HashSet ignoredTests = new HashSet<>(); static { - // add ignored tests here - // ignoredTests.add("testMyIgnoredTest"); + // add ignored tests here + // ignoredTests.add("testMyIgnoredTest"); + ignoredTests.add("testCalibration"); // not from command line } - private final static int N = 1000; + private final static String EXEC_MODE_ROBOT = "robot"; + private final static String EXEC_MODE_BUFFER = "buffer"; + private final static String EXEC_MODE_VOLATILE = "volatile"; + private final static String EXEC_MODE_DEFAULT = EXEC_MODE_ROBOT; + + public final static List EXEC_MODES = Arrays.asList(EXEC_MODE_ROBOT, EXEC_MODE_BUFFER, EXEC_MODE_VOLATILE); + + private static String EXEC_MODE = EXEC_MODE_DEFAULT; + + private final static String GC_MODE_DEF = "def"; + private final static String GC_MODE_ALL = "all"; + + private static String GC_MODE = GC_MODE_DEF; + + // System properties: + private final static boolean CALIBRATION = "true".equalsIgnoreCase(System.getProperty("CALIBRATION", "false")); + private final static boolean REPORT_OVERALL_FPS = "true".equalsIgnoreCase(System.getProperty("REPORT_OVERALL_FPS", "false")); + + private final static boolean DUMP_SAMPLES = "true".equalsIgnoreCase(System.getProperty("DUMP_SAMPLES", "false")); + private final static boolean TRACE = "true".equalsIgnoreCase(System.getProperty("TRACE", "false")); + private final static boolean TRACE_CONFIGURE = "true".equalsIgnoreCase(System.getProperty("TRACE_CONFIGURE", "false")); + private final static boolean TRACE_SYNC = "true".equalsIgnoreCase(System.getProperty("TRACE_SYNC", "false")); + + private final static boolean DELAY_START = "true".equalsIgnoreCase(System.getProperty("DelayStart", "false")); + private final static boolean DELAY_TEST = "true".equalsIgnoreCase(System.getProperty("DelayTest", "false")); + + private final static boolean ROBOT_TIME_DELAY = "true".equalsIgnoreCase(System.getProperty("ROBOT_TIME_DELAY", "true")); + private final static boolean ROBOT_TIME_ROUND = "true".equalsIgnoreCase(System.getProperty("ROBOT_TIME_ROUND", "false")); + + private final static boolean TEXT_VERSION = "true".equalsIgnoreCase(System.getProperty("TEXT_VERSION", "true")); + + // time scale multiplier to get more samples so refined metrics: + private final static int TIME_SCALE = Integer.getInteger("TIME_SCALE", 1); + + // default settings: + private static boolean VERBOSE = false; + private static boolean VERBOSE_FONT_CONFIG = false; + private static boolean VERBOSE_GRAPHICS_CONFIG = false; + + private static int REPEATS = 1; + + private static boolean USE_FPS = true; + + private static int NW = 1; + + private final static int N_DEFAULT = 1000; + private static int N = N_DEFAULT; private final static float WIDTH = 800; private final static float HEIGHT = 800; private final static float R = 25; private final static int BW = 50; private final static int BH = 50; - private final static int COUNT = 600; - private final static int CYCLE_DELAY = 3; - private final static int MAX_FRAME_CYCLES = 3000/CYCLE_DELAY; + private final static int IMAGE_W = (int) (WIDTH + BW); + private final static int IMAGE_H = (int) (HEIGHT + BH); + + // Test attributes: + private static String TEXT_STR = TEXT_VERSION ? VERSION : "The quick brown fox jumps over the lazy dog"; + + private static String TEXT_FONT = Font.DIALOG; + private static int TEXT_SIZE_DEFAULT = 12; + private static int TEXT_SIZE_LARGE = 32; + + private final static int COUNT = 600 * TIME_SCALE; + private final static int MIN_COUNT = 20; + private final static int MAX_SAMPLE_COUNT = 2 * COUNT; + + private static int WARMUP_COUNT = MIN_COUNT; + + private final static int DELAY = 1; + private final static int CYCLE_DELAY = DELAY; + + private final static long MIN_MEASURE_TIME_NS = 1000L * 1000 * 1000 * TIME_SCALE; // 1s min + private final static long MAX_MEASURE_TIME_NS = 6000L * 1000 * 1000 * TIME_SCALE; // 6s max + private final static int MAX_FRAME_CYCLES = 1000 * TIME_SCALE / CYCLE_DELAY; private final static int COLOR_TOLERANCE = 10; - private final static int MAX_MEASURE_CYCLES = 6000/CYCLE_DELAY; - private final static Color[] marker = {Color.RED, Color.BLUE, Color.GREEN, Color.YELLOW, Color.ORANGE, Color.MAGENTA}; + private final static Color[] MARKER = {Color.RED, Color.BLUE, Color.GREEN}; + + private final static Toolkit TOOLKIT = Toolkit.getDefaultToolkit(); + + private final static long FRAME_MAX = 60; + private final static long FRAME_PREC_IN_NANOS = (1000L * 1000 * 1000) / (2L * FRAME_MAX); interface Configurable { - void configure(Graphics2D g2d); + void configure(Graphics2D g2d, boolean enabled); } - interface Renderable { - void setup(Graphics2D g2d); - void render(Graphics2D g2d); - void update(); + final static class ConfigurableAA implements Configurable { + @Override + public void configure(final Graphics2D g2d, final boolean enabled) { + g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, + enabled ? RenderingHints.VALUE_ANTIALIAS_ON + : RenderingHints.VALUE_ANTIALIAS_OFF); + } + } + + final static class ConfigurableTextAA implements Configurable { + @Override + public void configure(final Graphics2D g2d, final boolean enabled) { + g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, + enabled ? RenderingHints.VALUE_TEXT_ANTIALIAS_ON + : RenderingHints.VALUE_TEXT_ANTIALIAS_OFF); + } + } + + final static class ConfigurableTextLCD implements Configurable { + @Override + public void configure(final Graphics2D g2d, final boolean enabled) { + g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, + enabled ? RenderingHints.VALUE_TEXT_ANTIALIAS_LCD_HRGB + : RenderingHints.VALUE_TEXT_ANTIALIAS_OFF); + } + } + + final static class ConfigurableXORMode implements Configurable { + @Override + public void configure(final Graphics2D g2d, final boolean enabled) { + if (enabled) { + g2d.setXORMode(Color.WHITE); + } else { + g2d.setPaintMode(); + } + } + } + + final static class ConfigurableXORModeTextLCD implements Configurable { + @Override + public void configure(final Graphics2D g2d, final boolean enabled) { + if (enabled) { + g2d.setXORMode(Color.WHITE); + } else { + g2d.setPaintMode(); + } + g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, + enabled ? RenderingHints.VALUE_TEXT_ANTIALIAS_LCD_HRGB + : RenderingHints.VALUE_TEXT_ANTIALIAS_DEFAULT); + } } - static class Particles { - private float[] bx; - private float[] by; - private float[] vx; - private float[] vy; - private float r; - private int n; + final static class Particles { + private final float[] bx; + private final float[] by; + private final float[] vx; + private final float[] vy; + private final float r; + private final int n; - private float x0; - private float y0; - private float width; - private float height; + private final float x0; + private final float y0; + private final float width; + private final float height; Particles(int n, float r, float x0, float y0, float width, float height) { bx = new float[n]; @@ -143,31 +295,35 @@ void update() { by[i] += vy[i]; if (by[i] + r > height || by[i] - r < y0) vy[i] = -vy[i]; } - } - } - ParticleRenderable createPR(ParticleRenderer renderer) { - return new ParticleRenderable(renderer); + interface Renderable { + void setup(Graphics2D g2d, boolean enabled); + + void render(Graphics2D g2d); + + void update(); } - static class ParticleRenderable implements Renderable { - ParticleRenderer renderer; - Configurable configure; + final static class ParticleRenderable implements Renderable { + final Particles balls; + final ParticleRenderer renderer; + Configurable configure = null; - ParticleRenderable(ParticleRenderer renderer, Configurable configure) { + ParticleRenderable(final Particles balls, final ParticleRenderer renderer) { + this.balls = balls; this.renderer = renderer; - this.configure = configure; - } - - ParticleRenderable(ParticleRenderer renderer) { - this(renderer, null); } @Override - public void setup(Graphics2D g2d) { - if (configure != null) configure.configure(g2d); + public void setup(final Graphics2D g2d, final boolean enabled) { + if (configure != null) { + if (TRACE_CONFIGURE) { + System.out.println("configure(" + configure.getClass().getSimpleName() + "): " + enabled); + } + configure.configure(g2d, enabled); + } } @Override @@ -180,7 +336,7 @@ public void update() { balls.update(); } - public ParticleRenderable configure(Configurable configure) { + public ParticleRenderable configure(final Configurable configure) { this.configure = configure; return this; } @@ -188,7 +344,46 @@ public ParticleRenderable configure(Configurable configure) { interface ParticleRenderer { void render(Graphics2D g2d, int id, float[] x, float[] y, float[] vx, float[] vy); + } + + final static class CalibrationParticleRenderer implements ParticleRenderer { + + CalibrationParticleRenderer() { + } + + @Override + public void render(Graphics2D g2d, int id, float[] x, float[] y, float[] vx, float[] vy) { + // no-op + } + } + + final static class MixedParticleRenderer implements ParticleRenderer { + + private final ParticleRenderer[] renderers; + + MixedParticleRenderer(ParticleRenderer... renderers) { + this.renderers = renderers; + } + + @Override + public void render(Graphics2D g2d, int id, float[] x, float[] y, float[] vx, float[] vy) { + renderers[id % renderers.length].render(g2d, id, x, y, vx, vy); + } + } + + final static class BatchedParticleRenderer implements ParticleRenderer { + + private final ParticleRenderer[] renderers; + BatchedParticleRenderer(ParticleRenderer... renderers) { + this.renderers = renderers; + } + + @Override + public void render(Graphics2D g2d, int id, float[] x, float[] y, float[] vx, float[] vy) { + final int step = N / renderers.length; + renderers[(id / step) % renderers.length].render(g2d, id, x, y, vx, vy); + } } static class FlatParticleRenderer implements ParticleRenderer { @@ -207,7 +402,7 @@ static class FlatParticleRenderer implements ParticleRenderer { @Override public void render(Graphics2D g2d, int id, float[] x, float[] y, float[] vx, float[] vy) { g2d.setColor(colors[id % colors.length]); - g2d.fillOval((int)(x[id] - r), (int)(y[id] - r), (int)(2*r), (int)(2*r)); + g2d.fillOval((int) (x[id] - r), (int) (y[id] - r), (int) (2 * r), (int) (2 * r)); } } @@ -228,11 +423,18 @@ public void render(Graphics2D g2d, int id, float[] x, float[] y, float[] vx, flo } } + static class WhiteTextParticleRenderer implements ParticleRenderer { - float r; + final float r; + final Font font; WhiteTextParticleRenderer(float r) { + this(r, TEXT_SIZE_DEFAULT); + } + + WhiteTextParticleRenderer(float r, int fontSize) { this.r = r; + font = new Font(TEXT_FONT, Font.PLAIN, fontSize); } void setPaint(Graphics2D g2d, int id) { @@ -242,12 +444,10 @@ void setPaint(Graphics2D g2d, int id) { @Override public void render(Graphics2D g2d, int id, float[] x, float[] y, float[] vx, float[] vy) { setPaint(g2d, id); - g2d.drawString("The quick brown fox jumps over the lazy dog", - (int)(x[id] - r), (int)(y[id] - r)); - g2d.drawString("The quick brown fox jumps over the lazy dog", - (int)(x[id] - r), (int)y[id]); - g2d.drawString("The quick brown fox jumps over the lazy dog", - (int)(x[id] - r), (int)(y[id] + r)); + g2d.setFont(font); + g2d.drawString(TEXT_STR, (int) (x[id] - r), (int) (y[id] - r)); + g2d.drawString(TEXT_STR, (int) (x[id] - r), (int) y[id]); + g2d.drawString(TEXT_STR, (int) (x[id] - r), (int) (y[id] + r)); } } @@ -257,7 +457,11 @@ static class TextParticleRenderer extends WhiteTextParticleRenderer { float r; TextParticleRenderer(int n, float r) { - super(r); + this(n,r, TEXT_SIZE_DEFAULT); + } + + TextParticleRenderer(int n, float r, int fontSize) { + super(r, fontSize); colors = new Color[n]; this.r = r; for (int i = 0; i < n; i++) { @@ -274,27 +478,18 @@ void setPaint(Graphics2D g2d, int id) { static class LargeTextParticleRenderer extends TextParticleRenderer { LargeTextParticleRenderer(int n, float r) { - super(n, r); + super(n, r, TEXT_SIZE_LARGE); } @Override public void render(Graphics2D g2d, int id, float[] x, float[] y, float[] vx, float[] vy) { - setPaint(g2d, id); if (id % 100 != 0) return; - Font font = new Font("LucidaGrande", Font.PLAIN, 32); - g2d.setFont(font); - g2d.drawString("The quick brown fox jumps over the lazy dog", - (int)(x[id] - r), (int)(y[id] - r)); - g2d.drawString("The quick brown fox jumps over the lazy dog", - (int)(x[id] - r), (int)y[id]); - g2d.drawString("The quick brown fox jumps over the lazy dog", - (int)(x[id] - r), (int)(y[id] + r)); + super.render(g2d, id, x, y, vx, vy); } } static class FlatOvalRotParticleRenderer extends FlatParticleRenderer { - FlatOvalRotParticleRenderer(int n, float r) { super(n, r); } @@ -314,10 +509,10 @@ public void render(Graphics2D g2d, int id, float[] x, float[] y, float[] vx, flo } g2d.translate(x[id], y[id]); g2d.rotate(Math.acos(l)); - g2d.fillOval(-(int)r, (int)(-0.5*r), (int) (2 * r), (int)r); + g2d.fillOval(-(int) r, (int) (-0.5 * r), (int) (2 * r), (int) r); g2d.setTransform(t); } else { - g2d.fillOval((int)(x[id] - r), (int)(y[id] - 0.5*r), + g2d.fillOval((int) (x[id] - r), (int) (y[id] - 0.5 * r), (int) (2 * r), (int) r); } } @@ -325,48 +520,43 @@ public void render(Graphics2D g2d, int id, float[] x, float[] y, float[] vx, flo static class LinGradOvalRotParticleRenderer extends FlatOvalRotParticleRenderer { - LinGradOvalRotParticleRenderer(int n, float r) { super(n, r); } @Override void setPaint(Graphics2D g2d, int id) { - Point2D start = new Point2D.Double(- r, - 0.5*r); - Point2D end = new Point2D.Double( 2 * r, r); + Point2D start = new Point2D.Double(-r, -0.5 * r); + Point2D end = new Point2D.Double(2 * r, r); float[] dist = {0.0f, 1.0f}; - Color[] cls = {colors[id %colors.length], colors[(colors.length - id) %colors.length]}; - LinearGradientPaint p = - new LinearGradientPaint(start, end, dist, cls); + Color[] cls = {colors[id % colors.length], colors[(colors.length - id) % colors.length]}; + LinearGradientPaint p = new LinearGradientPaint(start, end, dist, cls); g2d.setPaint(p); } } static class LinGrad3OvalRotParticleRenderer extends FlatOvalRotParticleRenderer { - LinGrad3OvalRotParticleRenderer(int n, float r) { super(n, r); } @Override void setPaint(Graphics2D g2d, int id) { - Point2D start = new Point2D.Double(- r, - 0.5*r); - Point2D end = new Point2D.Double( 2 * r, r); + Point2D start = new Point2D.Double(-r, -0.5 * r); + Point2D end = new Point2D.Double(2 * r, r); float[] dist = {0.0f, 0.5f, 1.0f}; Color[] cls = { - colors[id %colors.length], - colors[(colors.length - id) %colors.length], - colors[(id*5) %colors.length]}; - LinearGradientPaint p = - new LinearGradientPaint(start, end, dist, cls); + colors[id % colors.length], + colors[(colors.length - id) % colors.length], + colors[(id * 5) % colors.length]}; + LinearGradientPaint p = new LinearGradientPaint(start, end, dist, cls); g2d.setPaint(p); } } static class RadGrad3OvalRotParticleRenderer extends FlatOvalRotParticleRenderer { - RadGrad3OvalRotParticleRenderer(int n, float r) { super(n, r); } @@ -376,36 +566,33 @@ void setPaint(Graphics2D g2d, int id) { Point2D start = new Point2D.Double(); float[] dist = {0.0f, 0.5f, 1.0f}; Color[] cls = { - colors[id %colors.length], - colors[(colors.length - id) %colors.length], - colors[(id*5) %colors.length]}; - RadialGradientPaint p = - new RadialGradientPaint(start, r, dist, cls); + colors[id % colors.length], + colors[(colors.length - id) % colors.length], + colors[(id * 5) % colors.length]}; + RadialGradientPaint p = new RadialGradientPaint(start, r, dist, cls); g2d.setPaint(p); } } static class FlatBoxParticleRenderer extends FlatParticleRenderer { - FlatBoxParticleRenderer(int n, float r) { super(n, r); } + @Override public void render(Graphics2D g2d, int id, float[] x, float[] y, float[] vx, float[] vy) { g2d.setColor(colors[id % colors.length]); - g2d.fillRect((int)(x[id] - r), (int)(y[id] - r), (int)(2*r), (int)(2*r)); - + g2d.fillRect((int) (x[id] - r), (int) (y[id] - r), (int) (2 * r), (int) (2 * r)); } - } static class ClipFlatBoxParticleRenderer extends FlatParticleRenderer { - ClipFlatBoxParticleRenderer(int n, float r) { super(n, r); } + @Override public void render(Graphics2D g2d, int id, float[] x, float[] y, float[] vx, float[] vy) { if ((id % 10) == 0) { @@ -434,17 +621,54 @@ static class ImgParticleRenderer extends FlatParticleRenderer { @Override public void render(Graphics2D g2d, int id, float[] x, float[] y, float[] vx, float[] vy) { g2d.setColor(colors[id % colors.length]); - g2d.drawImage(dukeImg, (int)(x[id] - r), (int)(y[id] - r), (int)(2*r), (int)(2*r), null); + g2d.drawImage(dukeImg, (int) (x[id] - r), (int) (y[id] - r), (int) (2 * r), (int) (2 * r), null); } } - static class FlatBoxRotParticleRenderer extends FlatParticleRenderer { + static class VolImgParticleRenderer extends ImgParticleRenderer { + VolatileImage volImg; + + VolImgParticleRenderer(int n, float r) { + super(n, r); + } + + @Override + public void render(Graphics2D g2d, int id, float[] x, float[] y, float[] vx, float[] vy) { + GraphicsConfiguration config = g2d.getDeviceConfiguration(); + if (volImg == null) { + volImg = config.createCompatibleVolatileImage(dukeImg.getWidth(), dukeImg.getHeight(), + Transparency.TRANSLUCENT); + Graphics2D g = volImg.createGraphics(); + g.setComposite(AlphaComposite.Src); + g.drawImage(dukeImg, null, null); + g.dispose(); + } else { + int status = volImg.validate(config); + if (status == VolatileImage.IMAGE_INCOMPATIBLE) { + volImg = config.createCompatibleVolatileImage(dukeImg.getWidth(), dukeImg.getHeight(), + Transparency.TRANSLUCENT); + } + if (status != VolatileImage.IMAGE_OK) { + Graphics2D g = volImg.createGraphics(); + g.setComposite(AlphaComposite.Src); + g.drawImage(dukeImg, null, null); + g.dispose(); + } + } + Composite savedComposite = g2d.getComposite(); + g2d.setComposite(AlphaComposite.SrcOver); + g2d.drawImage(volImg, (int) (x[id] - r), (int) (y[id] - r), (int) (2 * r), (int) (2 * r), null); + g2d.setComposite(savedComposite); + } + } + static class FlatBoxRotParticleRenderer extends FlatParticleRenderer { FlatBoxRotParticleRenderer(int n, float r) { super(n, r); } + @Override public void render(Graphics2D g2d, int id, float[] x, float[] y, float[] vx, float[] vy) { g2d.setColor(colors[id % colors.length]); @@ -456,10 +680,10 @@ public void render(Graphics2D g2d, int id, float[] x, float[] y, float[] vx, flo } g2d.translate(x[id], y[id]); g2d.rotate(Math.acos(l)); - g2d.fillRect(-(int)r, -(int)r, (int) (2 * r), (int) (2 * r)); + g2d.fillRect(-(int) r, -(int) r, (int) (2 * r), (int) (2 * r)); g2d.setTransform(t); } else { - g2d.fillRect((int)(x[id] - r), (int)(y[id] - r), + g2d.fillRect((int) (x[id] - r), (int) (y[id] - r), (int) (2 * r), (int) (2 * r)); } } @@ -467,17 +691,17 @@ public void render(Graphics2D g2d, int id, float[] x, float[] y, float[] vx, flo static class WiredParticleRenderer extends FlatParticleRenderer { - WiredParticleRenderer(int n, float r) { super(n, r); } + @Override public void render(Graphics2D g2d, int id, float[] x, float[] y, float[] vx, float[] vy) { g2d.setColor(colors[id % colors.length]); - g2d.drawOval((int)(x[id] - r), (int)(y[id] - r), (int)(2*r), (int)(2*r)); + g2d.drawOval((int) (x[id] - r), (int) (y[id] - r), (int) (2 * r), (int) (2 * r)); } - } + static class WiredBoxParticleRenderer extends FlatParticleRenderer { WiredBoxParticleRenderer(int n, float r) { @@ -487,10 +711,10 @@ static class WiredBoxParticleRenderer extends FlatParticleRenderer { @Override public void render(Graphics2D g2d, int id, float[] x, float[] y, float[] vx, float[] vy) { g2d.setColor(colors[id % colors.length]); - g2d.drawRect((int)(x[id] - r), (int)(y[id] - r), (int)(2*r), (int)(2*r)); + g2d.drawRect((int) (x[id] - r), (int) (y[id] - r), (int) (2 * r), (int) (2 * r)); } - } + static class SegParticleRenderer extends FlatParticleRenderer { SegParticleRenderer(int n, float r) { @@ -499,17 +723,15 @@ static class SegParticleRenderer extends FlatParticleRenderer { @Override public void render(Graphics2D g2d, int id, float[] x, float[] y, float[] vx, float[] vy) { - double v = Math.sqrt(vx[id]*vx[id]+vy[id]*vy[id]); - float nvx = (float) (vx[id]/v); - float nvy = (float) (vy[id]/v); + double v = Math.sqrt(vx[id] * vx[id] + vy[id] * vy[id]); + float nvx = (float) (vx[id] / v); + float nvy = (float) (vy[id] / v); g2d.setColor(colors[id % colors.length]); - g2d.drawLine((int)(x[id] - r*nvx), (int)(y[id] - r*nvy), - (int)(x[id] + 2*r*nvx), (int)(y[id] + 2*r*nvy)); + g2d.drawLine((int) (x[id] - r * nvx), (int) (y[id] - r * nvy), + (int) (x[id] + 2 * r * nvx), (int) (y[id] + 2 * r * nvy)); } - } - static class WiredQuadParticleRenderer extends FlatParticleRenderer { WiredQuadParticleRenderer(int n, float r) { @@ -520,9 +742,8 @@ static class WiredQuadParticleRenderer extends FlatParticleRenderer { public void render(Graphics2D g2d, int id, float[] x, float[] y, float[] vx, float[] vy) { if (id > 2 && (id % 3) == 0) { g2d.setColor(colors[id % colors.length]); - g2d.draw(new QuadCurve2D.Float(x[id-3], y[id-3], x[id-2], y[id-2], x[id-1], y[id-1])); + g2d.draw(new QuadCurve2D.Float(x[id - 3], y[id - 3], x[id - 2], y[id - 2], x[id - 1], y[id - 1])); } - } } @@ -536,9 +757,8 @@ static class FlatQuadParticleRenderer extends FlatParticleRenderer { public void render(Graphics2D g2d, int id, float[] x, float[] y, float[] vx, float[] vy) { if (id > 2 && (id % 3) == 0) { g2d.setColor(colors[id % colors.length]); - g2d.fill(new QuadCurve2D.Float(x[id-3], y[id-3], x[id-2], y[id-2], x[id-1], y[id-1])); + g2d.fill(new QuadCurve2D.Float(x[id - 3], y[id - 3], x[id - 2], y[id - 2], x[id - 1], y[id - 1])); } - } } @@ -553,7 +773,7 @@ static class BlitImageParticleRenderer extends FlatParticleRenderer { @Override public void render(Graphics2D g2d, int id, float[] x, float[] y, float[] vx, float[] vy) { - g2d.drawImage(image, (int)(x[id] - r), (int)(y[id] - r), (int)(2*r), (int)(2*r), null); + g2d.drawImage(image, (int) (x[id] - r), (int) (y[id] - r), (int) (2 * r), (int) (2 * r), null); } private static void fill(final Image image) { @@ -565,7 +785,6 @@ private static void fill(final Image image) { } graphics.dispose(); } - } static class SwBlitImageParticleRenderer extends BlitImageParticleRenderer { @@ -602,398 +821,1594 @@ private static BufferedImage makeManagedBI(final int type) { } } - static class PerfMeter { - private String name; + final static class PerfMeter { + + private final FrameHandler fh; + private final String name; + private final PerfMeterExecutor executor; + PerfMeter(final FrameHandler fh, String name) { + this.fh = fh; + this.name = name; + executor = getExecutor(); + } - private JPanel panel; + void exec(final Renderable renderable) throws Exception { + executor.exec(name, renderable); + } - private double execTime = 0; - private AtomicInteger markerIdx = new AtomicInteger(0); - private int renderedMarkerIdx = -1; - private AtomicLong markerPaintTime = new AtomicLong(0); + private PerfMeterExecutor getExecutor() { + switch (EXEC_MODE) { + default: + case EXEC_MODE_ROBOT: + return new PerfMeterRobot(fh); + case EXEC_MODE_BUFFER: + fh.prepareImageProvider(false); + return new PerfMeterImageProvider(fh); + case EXEC_MODE_VOLATILE: + fh.prepareImageProvider(true); + return new PerfMeterImageProvider(fh); + } + } + } - private double fps; - private int skippedFrame = 0; + static void paintTest(final Renderable renderable, final Graphics2D g2d, + final Color markerColor, final boolean doSync) { + // clip to frame: + g2d.setClip(0, 0, IMAGE_W, IMAGE_H); + // clear background: + g2d.setColor(Color.BLACK); + g2d.fillRect(0, 0, IMAGE_W, IMAGE_H); - PerfMeter(String name) { - this.name = name; + // render test: + renderable.setup(g2d, true); + renderable.render(g2d); + renderable.setup(g2d, false); + + // draw marker at end: + g2d.setClip(0, 0, BW, BH); + g2d.setColor(markerColor); + g2d.fillRect(0, 0, BW, BH); + + if (doSync) { + // synchronize toolkit: + TOOLKIT.sync(); } + } - PerfMeter exec(final Renderable renderable) throws Exception { - final CountDownLatch latchFrame = new CountDownLatch(1); + final static class FrameHandler { - final JFrame f = new JFrame(); - f.addWindowListener(new WindowAdapter() { - @Override - public void windowClosed(WindowEvent e) { - latchFrame.countDown(); - } - }); + private boolean calibrate = VERBOSE; - SwingUtilities.invokeAndWait(new Runnable() { - @Override - public void run() { + private int threadId = -1; + private int frameId = -1; - panel = new JPanel() { - @Override - protected void paintComponent(Graphics g) { - super.paintComponent(g); - int idx = markerIdx.get(); - if (idx != renderedMarkerIdx) { - markerPaintTime.set(System.nanoTime()); - } + private final GraphicsConfiguration gc; - Graphics2D g2d = (Graphics2D) g.create(); - renderable.setup(g2d); - renderable.render(g2d); - g2d.setClip(null); - g2d.setPaintMode(); - g2d.setColor(marker[idx]); - g2d.fillRect(0, 0, BW, BH); - renderedMarkerIdx = idx; - } - }; + private JFrame frame = null; - panel.setPreferredSize(new Dimension((int) (WIDTH + BW), (int) (HEIGHT + BH))); - panel.setBackground(Color.BLACK); - f.add(panel); - f.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); - f.pack(); - f.setVisible(true); - } - }); + private final CountDownLatch latchShownFrame = new CountDownLatch(1); + private final CountDownLatch latchClosedFrame = new CountDownLatch(1); - Robot robot = new Robot(); - int cycle = 0; - int frame = 0; - long paintTime = 0; - int maxFrameCycle = -1; - while (frame < COUNT) { - long t; - if ((t = markerPaintTime.getAndSet(0)) > 0) { - paintTime = t; - maxFrameCycle = cycle + MAX_FRAME_CYCLES; - } + private ImageProvider imageProvider = null; - if (paintTime > 0) { - Color c = robot.getPixelColor( - panel.getTopLevelAncestor().getX() + panel.getTopLevelAncestor().getInsets().left + BW / 2, - panel.getTopLevelAncestor().getY() + panel.getTopLevelAncestor().getInsets().top + BW / 2); - - if (isAlmostEqual(c, marker[markerIdx.get()])) { - execTime += System.nanoTime() - paintTime; - frame++; - paintTime = 0; - maxFrameCycle = -1; - markerIdx.accumulateAndGet(marker.length, (x, y) -> (x + 1) % y); - renderable.update(); - panel.getParent().repaint(); - } else if (cycle >= maxFrameCycle) { - skippedFrame++; - paintTime = 0; - maxFrameCycle = -1; - markerIdx.accumulateAndGet(marker.length, (x, y) -> (x + 1) % y); - panel.getParent().repaint(); + FrameHandler(GraphicsConfiguration gc) { + this.gc = gc; + } + + void setIds(int threadId, int frameId) { + this.threadId = threadId; + this.frameId = frameId; + } + + void prepareFrameEDT(final String title) { + if (frame == null) { + frame = new JFrame(gc); + frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); + frame.addComponentListener(new ComponentAdapter() { + @Override + public void componentShown(ComponentEvent e) { + latchShownFrame.countDown(); } - } - try { - Thread.sleep(CYCLE_DELAY); - } catch (InterruptedException ex) { - ex.printStackTrace(); - } - if (cycle >= MAX_MEASURE_CYCLES) { - break; - } - cycle++; + }); + frame.addWindowListener(new WindowAdapter() { + @Override + public void windowClosed(WindowEvent e) { + latchClosedFrame.countDown(); + } + }); } - SwingUtilities.invokeAndWait(() -> { - f.setVisible(false); - f.dispose(); - }); + frame.setTitle(title); + } - latchFrame.await(); - if (execTime != 0 && frame != 0) { - fps = 1e9 / (execTime / frame); - } else { - fps = 0; + void showFrameEDT(final JPanel panel) { + if (frame != null) { + panel.setPreferredSize(new Dimension(IMAGE_W, IMAGE_H)); + panel.setBackground(Color.BLACK); + + frame.getContentPane().removeAll(); + frame.getContentPane().add(panel); + frame.getContentPane().revalidate(); + + if (!frame.isVisible()) { + if (frameId != -1) { + final int off = (frameId - 1) * 100; + final Rectangle gcBounds = gc.getBounds(); + final int xoff = gcBounds.x + off; + final int yoff = gcBounds.y + off; + + if ((xoff != 0) || (yoff != 0)) { + frame.setLocation(xoff, yoff); + } + } + frame.pack(); + frame.setVisible(true); + } } + } - return this; + void waitFrameShown() throws Exception { + latchShownFrame.await(); + } + + void resetFrame() throws Exception { + if (frame != null) { + SwingUtilities.invokeAndWait(new Runnable() { + @Override + public void run() { + frame.getContentPane().removeAll(); + frame.getContentPane().revalidate(); + } + }); + } } - private void report() { - if (skippedFrame > 0) { - System.err.println(skippedFrame + " frame(s) skipped"); + void repaintFrame() throws Exception { + if (frame != null) { + SwingUtilities.invokeAndWait(new Runnable() { + @Override + public void run() { + frame.repaint(); + } + }); } - System.err.println(name + " : " + String.format("%.2f FPS", fps)); } - private boolean isAlmostEqual(Color c1, Color c2) { - return Math.abs(c1.getRed() - c2.getRed()) < COLOR_TOLERANCE && - Math.abs(c1.getGreen() - c2.getGreen()) < COLOR_TOLERANCE && - Math.abs(c1.getBlue() - c2.getBlue()) < COLOR_TOLERANCE; + private void waitFrameHidden() throws Exception { + latchClosedFrame.await(); + } + + void hideFrameAndWait() throws Exception { + if (frame != null) { + SwingUtilities.invokeAndWait(new Runnable() { + @Override + public void run() { + frame.setVisible(false); + frame.dispose(); + frame = null; + } + }); + waitFrameHidden(); + } + } + void prepareImageProvider(final boolean useVolatile) { + if (this.imageProvider == null) { + this.imageProvider = new ImageProvider(useVolatile); + } } } - private static final Particles balls = new Particles(N, R, BW, BH, WIDTH, HEIGHT); - private static final ParticleRenderer flatRenderer = new FlatParticleRenderer(N, R); - private static final ParticleRenderer clipFlatRenderer = new ClipFlatParticleRenderer(N, R); - private static final ParticleRenderer flatOvalRotRenderer = new FlatOvalRotParticleRenderer(N, R); - private static final ParticleRenderer flatBoxRenderer = new FlatBoxParticleRenderer(N, R); - private static final ParticleRenderer clipFlatBoxParticleRenderer = new ClipFlatBoxParticleRenderer(N, R); - private static final ParticleRenderer flatBoxRotRenderer = new FlatBoxRotParticleRenderer(N, R); - private static final ParticleRenderer linGradOvalRotRenderer = new LinGradOvalRotParticleRenderer(N, R); - private static final ParticleRenderer linGrad3OvalRotRenderer = new LinGrad3OvalRotParticleRenderer(N, R); - private static final ParticleRenderer radGrad3OvalRotRenderer = new RadGrad3OvalRotParticleRenderer(N, R); - private static final ParticleRenderer wiredRenderer = new WiredParticleRenderer(N, R); - private static final ParticleRenderer wiredBoxRenderer = new WiredBoxParticleRenderer(N, R); - private static final ParticleRenderer segRenderer = new SegParticleRenderer(N, R); - private static final ParticleRenderer flatQuadRenderer = new FlatQuadParticleRenderer(N, R); - private static final ParticleRenderer wiredQuadRenderer = new WiredQuadParticleRenderer(N, R); - private static final ParticleRenderer imgRenderer = new ImgParticleRenderer(N, R); - private static final ParticleRenderer textRenderer = new TextParticleRenderer(N, R); - private static final ParticleRenderer largeTextRenderer = new LargeTextParticleRenderer(N, R); - private static final ParticleRenderer whiteTextRenderer = new WhiteTextParticleRenderer(R); - private static final ParticleRenderer argbSwBlitImageRenderer = new SwBlitImageParticleRenderer(N, R, BufferedImage.TYPE_INT_ARGB); - private static final ParticleRenderer bgrSwBlitImageRenderer = new SwBlitImageParticleRenderer(N, R, BufferedImage.TYPE_INT_BGR); - private static final ParticleRenderer argbSurfaceBlitImageRenderer = new SurfaceBlitImageParticleRenderer(N, R, BufferedImage.TYPE_INT_ARGB); - private static final ParticleRenderer bgrSurfaceBlitImageRenderer = new SurfaceBlitImageParticleRenderer(N, R, BufferedImage.TYPE_INT_BGR); + static abstract class PerfMeterExecutor { - private static final Configurable AA = (Graphics2D g2d) -> - g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, - RenderingHints.VALUE_ANTIALIAS_ON); + protected final static int SCORE_MAIN = 0; + protected final static int SCORE_ERROR = 1; + protected final static int SCORE_OTHER = 2; - private static final Configurable TextLCD = (Graphics2D g2d) -> - g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, - RenderingHints.VALUE_TEXT_ANTIALIAS_LCD_HRGB); + protected final static int PCT_00 = 0; + protected final static int PCT_10 = 1; + protected final static int PCT_25 = 2; + protected final static int PCT_50 = 3; + protected final static int PCT_75 = 4; + protected final static int PCT_90 = 5; + protected final static int PCT_100 = 6; - private static final Configurable TextAA = (Graphics2D g2d) -> - g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, - RenderingHints.VALUE_TEXT_ANTIALIAS_ON); + private final static IntBinaryOperator INC_MOD_FUNC = new IntBinaryOperator() { + public int applyAsInt(int x, int y) { + return (x + 1) % y; + } + }; - private static final Configurable XORMode = (Graphics2D g2d) -> - {g2d.setXORMode(Color.WHITE);}; + private static final AtomicInteger headerMark = new AtomicInteger(1); - private static final Configurable XORModeLCDText = (Graphics2D g2d) -> - {g2d.setXORMode(Color.WHITE); - g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, - RenderingHints.VALUE_TEXT_ANTIALIAS_LCD_HRGB);}; + /* members */ + protected final FrameHandler fh; + protected final boolean skipWait; + protected final AtomicInteger paintIdx = new AtomicInteger(0); + protected final AtomicInteger markerIdx = new AtomicInteger(0); + protected final AtomicLong markerStartTime = new AtomicLong(0); + protected final AtomicLong markerPaintTime = new AtomicLong(0); - public void testFlatOval() throws Exception { - (new PerfMeter("FlatOval")).exec(createPR(flatRenderer)).report(); - } + protected String name = null; + protected int skippedFrames = 0; - public void testFlatOvalAA() throws Exception { - (new PerfMeter("FlatOvalAA")).exec(createPR(flatRenderer).configure(AA)).report(); - } + protected int frames = 0; + // test timestamp data: + protected long[] testTimestamp = new long[MAX_SAMPLE_COUNT]; + // test duration data (ns): + protected long[] testTime = new long[MAX_SAMPLE_COUNT]; - public void testClipFlatOval() throws Exception { - (new PerfMeter("ClipFlatOval")).exec(createPR(clipFlatRenderer)).report(); - } + protected final double[] scores = new double[SCORE_OTHER + 1]; + protected final double[] results = new double[PCT_100 + 1]; - public void testClipFlatOvalAA() throws Exception { - (new PerfMeter("ClipFlatOvalAA")).exec(createPR(clipFlatRenderer).configure(AA)).report(); - } + protected PerfMeterExecutor(final boolean skipWait, final FrameHandler fh) { + this.skipWait = skipWait; + this.fh = fh; + } - public void testFlatBox() throws Exception { - (new PerfMeter("FlatBox")).exec(createPR(flatBoxRenderer)).report(); - } + protected void beforeExec() { + } - public void testFlatBoxAA() throws Exception { - (new PerfMeter("FlatBoxAA")).exec(createPR(flatBoxRenderer).configure(AA)).report(); - } + protected void afterExec() { + } - public void testClipFlatBox() throws Exception { - (new PerfMeter("ClipFlatBox")).exec(createPR(clipFlatBoxParticleRenderer)).report(); - } + protected void reset() { + paintIdx.set(0); + markerIdx.set(0); + markerStartTime.set(0); + markerPaintTime.set(0); + } - public void testClipFlatBoxAA() throws Exception { - (new PerfMeter("ClipFlatBoxAA")).exec(createPR(clipFlatBoxParticleRenderer).configure(AA)).report(); - } + protected void updateMarkerIdx() { + markerIdx.accumulateAndGet(MARKER.length, INC_MOD_FUNC); + } - public void testImage() throws Exception { - (new PerfMeter("Image")).exec(createPR(imgRenderer)).report(); - } + protected final void exec(final String testName, final Renderable renderable) throws Exception { + if (TRACE) System.out.print("\n!"); + this.name = testName + (isMultiThreads() ? ("-" + fh.threadId) : ""); - public void testImageAA() throws Exception { - (new PerfMeter("ImageAA")).exec(createPR(imgRenderer).configure(AA)).report(); - } + SwingUtilities.invokeAndWait(new Runnable() { + @Override + public void run() { + fh.prepareFrameEDT(name); + // call beforeExec() after frame is created: + beforeExec(); - public void testRotatedBox() throws Exception { - (new PerfMeter("RotatedBox")).exec(createPR(flatBoxRotRenderer)).report(); - } + final JPanel panel = new JPanel() { + @Override + protected void paintComponent(Graphics g) { + if (TRACE) System.out.print("P"); + paintPanel(renderable, g); + if (TRACE) System.out.print("Q"); + } + }; + fh.showFrameEDT(panel); + if (TRACE) System.out.print(">>"); + } + }); - public void testRotatedBoxAA() throws Exception { - (new PerfMeter("RotatedBoxAA")).exec(createPR(flatBoxRotRenderer).configure(AA)).report(); - } + // Wait frame to be shown: + fh.waitFrameShown(); - public void testRotatedOval() throws Exception { - (new PerfMeter("RotatedOval")).exec(createPR(flatOvalRotRenderer)).report(); - } + if (TRACE) System.out.print(":"); - public void testRotatedOvalAA() throws Exception { - (new PerfMeter("RotatedOvalAA")).exec(createPR(flatOvalRotRenderer).configure(AA)).report(); - } + // Reset before warmup: + reset(); - public void testLinGrad3RotatedOval() throws Exception { - (new PerfMeter("LinGrad3RotatedOval")).exec(createPR(linGrad3OvalRotRenderer)).report(); - } + if (WARMUP_COUNT > 0) { + // Warmup to prepare frame synchronization: + for (int i = 0; i < WARMUP_COUNT; i++) { + updateMarkerIdx(); + renderable.update(); + fh.repaintFrame(); + sleep(10); + while (markerStartTime.get() == 0) { + if (TRACE) System.out.print("-"); + sleep(1); + } + markerStartTime.set(0); + } + // Reset before measurements: + reset(); + } + if (TRACE) System.out.print(":>>"); - public void testLinGrad3RotatedOvalAA() throws Exception { - (new PerfMeter("LinGrad3RotatedOvalAA")).exec(createPR(linGrad3OvalRotRenderer).configure(AA)).report(); - } + // signal thread is ready for test + readyCount.countDown(); + if (TRACE_SYNC) traceSync(name + " ready => waiting start signal..."); - public void testRadGrad3RotatedOval() throws Exception { - (new PerfMeter("RadGrad3RotatedOval")).exec(createPR(radGrad3OvalRotRenderer)).report(); - } + // wait start signal: + triggerStart.await(); + // Run Benchmark (all threads): + if (TRACE_SYNC) traceSync(name + " benchmark started"); - public void testRadGrad3RotatedOvalAA() throws Exception { - (new PerfMeter("RadGrad3RotatedOvalAA")).exec(createPR(radGrad3OvalRotRenderer).configure(AA)).report(); - } + int cycles = 0; + frames = 0; + long paintStartTime = 0L; + long paintElapsedTime = 0L; + long lastFrameTime = 0L; - public void testLinGradRotatedOval() throws Exception { - (new PerfMeter("LinGradRotatedOval")).exec(createPR(linGradOvalRotRenderer)).report(); - } + final long startTime = System.nanoTime(); + final long minTime = startTime + MIN_MEASURE_TIME_NS; + final long endTime = startTime + MAX_MEASURE_TIME_NS; - public void testLinGradRotatedOvalAA() throws Exception { - (new PerfMeter("LinGradRotatedOvalAA")).exec(createPR(linGradOvalRotRenderer).configure(AA)).report(); + // Start 1st measurement: + fh.repaintFrame(); + + for (; ; ) { + long t; + if ((t = markerStartTime.getAndSet(0L)) > 0L) { + paintStartTime = t; + if (TRACE) System.out.print("|"); + } + + boolean wait = true; + + if (paintStartTime > 0L) { + // get optional elapsed time: + paintElapsedTime = markerPaintTime.get(); + + if (TRACE) System.out.print("."); + wait = !skipWait; + final Color c = getMarkerColor(); + + if (isAlmostEqual(c, MARKER[markerIdx.get()])) { + final long durationNs = getElapsedTime((paintElapsedTime != 0L) ? paintElapsedTime : paintStartTime); + if ((durationNs > 0L) && (frames < MAX_SAMPLE_COUNT)) { + testTimestamp[frames] = paintStartTime - startTime; + testTime[frames] = durationNs; + } + if (REPORT_OVERALL_FPS) { + lastFrameTime = System.nanoTime(); + } + if (TRACE) System.out.print("R"); + frames++; + paintStartTime = 0L; + paintElapsedTime = 0L; + cycles = 0; + updateMarkerIdx(); + renderable.update(); + fh.repaintFrame(); + } else if (cycles >= MAX_FRAME_CYCLES) { + if (TRACE) System.out.print("M"); + skippedFrames++; + paintStartTime = 0L; + paintElapsedTime = 0L; + cycles = 0; + updateMarkerIdx(); + fh.repaintFrame(); + } else { + if (TRACE) System.out.print("-"); + } + } + final long currentTime = System.nanoTime(); + if ((frames >= MIN_COUNT) && (currentTime >= endTime)) { + break; + } + if ((frames >= COUNT) && (currentTime >= minTime)) { + break; + } + if (wait) { + sleep(CYCLE_DELAY); + } + cycles++; + } // end measurements + + // signal test completed: + completedCount.countDown(); + if (TRACE_SYNC) traceSync(name + " completed => waiting stop signal..."); + + // wait stop signal: + triggerStop.await(); + // Stop Benchmark (all threads): + if (TRACE_SYNC) traceSync(name + " stopped"); + + if (DELAY_TEST) { + sleep(1000); + } + fh.resetFrame(); + + // Process results: + if (REPORT_OVERALL_FPS && (lastFrameTime != 0)) { + final double elapsedTime = (lastFrameTime - startTime); + final double elapsedFPS = 1000000000.0 * frames / elapsedTime; + + System.err.println(frames + " in " + (elapsedTime / 1000000) + " ms: ~ " + elapsedFPS + " FPS"); + } + + processTimes(); + + if (TRACE) System.out.print("<<\n"); + afterExec(); + + // Log header once: + if (headerMark.getAndDecrement() == 1) { + System.err.println(getHeader()); + } + + // Log report: + System.err.println(getResults()); + + // signal test done: + doneCount.countDown(); + if (TRACE_SYNC) traceSync(name + " done => waiting exit signal..."); + + // wait exit signal: + triggerExit.await(); + // Stop Benchmark (all threads): + if (TRACE_SYNC) traceSync(name + " exited"); + } + + protected abstract void paintPanel(final Renderable renderable, final Graphics g); + + protected abstract long getElapsedTime(long paintTime); + + protected abstract Color getMarkerColor() throws Exception; + + protected boolean isAlmostEqual(Color c1, Color c2) { + return (Math.abs(c1.getRed() - c2.getRed()) < COLOR_TOLERANCE) && + (Math.abs(c1.getGreen() - c2.getGreen()) < COLOR_TOLERANCE) && + (Math.abs(c1.getBlue() - c2.getBlue()) < COLOR_TOLERANCE); + } + + protected void processTimes() { + if (frames != 0) { + frames = Math.min(frames, MAX_SAMPLE_COUNT); + + if (DUMP_SAMPLES) { + // Dump all results: + final File file = new File("./rp-" + replaceNonFileNameChars(name) + "-samples.csv"); + System.out.println("Writing samples to : " + file.getAbsolutePath()); + + try (final PrintWriter w = new PrintWriter(file, Charset.forName("UTF-8"))) { + w.write("# "); + w.write(VERSION); + w.write(" - "); + w.write("Test: "); + w.write(name); + w.write('\n'); + + for (int i = 0; i < frames; i++) { + w.write(Double.toString(millis(testTimestamp[i]))); + w.write(','); + w.write(Double.toString(millis(testTime[i]))); + w.write('\n'); + } + } catch (IOException ioe) { + System.err.println("IO exception:"); + ioe.printStackTrace(); + } + } + // Ignore first 10% (warmup at the beginning): + final int first = (int) Math.floor(frames * 0.10); + final int last = frames - 1; + + // free testTimestamp to avoid any future usage: + testTimestamp = null; + + // note: testTime array is modified below: + // Sort values to get percentiles: + Arrays.sort(testTime, first, frames); + + final long[] pcts = getPercentiles(testTime, first, last); + + final long median = pcts[PCT_50]; + + if (USE_FPS) { + scores[SCORE_MAIN] = fps(median); + + results[PCT_100] = fps(pcts[PCT_00]); + results[PCT_90] = fps(pcts[PCT_10]); + results[PCT_75] = fps(pcts[PCT_25]); + results[PCT_50] = fps(pcts[PCT_50]); + results[PCT_25] = fps(pcts[PCT_75]); + results[PCT_10] = fps(pcts[PCT_90]); + results[PCT_00] = fps(pcts[PCT_100]); + + // STDDEV = IQR / 1.35 = (Q3 - Q1) * 20 / 27 + scores[SCORE_ERROR] = (results[PCT_75] - results[PCT_25]) * 20L / 27L; + scores[SCORE_OTHER] = millis(median); + } else { + scores[SCORE_MAIN] = millis(median); + + results[PCT_00] = millis(pcts[PCT_00]); + results[PCT_10] = millis(pcts[PCT_10]); + results[PCT_25] = millis(pcts[PCT_25]); + results[PCT_50] = millis(pcts[PCT_50]); + results[PCT_75] = millis(pcts[PCT_75]); + results[PCT_90] = millis(pcts[PCT_90]); + results[PCT_100] = millis(pcts[PCT_100]); + + // STDDEV = IQR / 1.35 = (Q3 - Q1) * 20 / 27 (normal distribution ?) + scores[SCORE_ERROR] = (results[PCT_75] - results[PCT_25]) * 20L / 27L; + scores[SCORE_OTHER] = fps(median); + + // System.out.println("stddev(IQR) = " + scores[SCORE_ERROR]); + + // MAD = Median Absolute Deviation: + for (int i = first; i <= last; i++) { + testTime[i] = Math.abs(testTime[i] - median); + } + // Sort values to get percentiles: + Arrays.sort(testTime, first, frames); + + // STDDEV = 1.4826 * MAD (normal distribution ?) + scores[SCORE_ERROR] = 1.4826 * millis(testTime[pctIndex(first, last, 0.50)]); // 50% (median) + + // System.out.println("stddev(MAD) = " + scores[SCORE_ERROR]); + } + // free testTime to avoid any future usage: + testTime = null; + } + } + + protected static String getHeader() { + if (VERBOSE) { + return String.format("%-25s : %s ± %s %s [%s] (p00: ... p10: ... p25: ... p50: ... p75: ... p90: ... p100: ... %s) (... frames)", + "Test Name", (USE_FPS ? "Median(FPS)" : "Median(TimeMs)"), (USE_FPS ? "Stddev(FPS)" : "Stddev(TimeMs)"), "Unit", + (!USE_FPS ? "Median(FPS)" : "Median(TimeMs)"), "Unit"); + } + return String.format("%-25s : %s ± %s %s", + "Test Name", (USE_FPS ? "Median(FPS)" : "Median(TimeMs)"), (USE_FPS ? "Stddev(FPS)" : "Stddev(TimeMs)"), "Unit"); + + } + + protected String getResults() { + if (skippedFrames > 0) { + System.err.println(name + " : " + skippedFrames + " frame(s) skipped"); + } + if (VERBOSE) { + return String.format("%-25s : %.3f ± %.3f %s [%.3f %s] (p00: %.3f p10: %.3f p25: %.3f p50: %.3f p75: %.3f p90: %.3f p100: %.3f %s) (%d frames)", + name, scores[SCORE_MAIN], scores[SCORE_ERROR], (USE_FPS ? "FPS" : "ms"), + scores[SCORE_OTHER], (USE_FPS ? "ms" : "FPS"), + results[PCT_00], results[PCT_10], results[PCT_25], results[PCT_50], results[PCT_75], results[PCT_90], results[PCT_100], + (USE_FPS ? "FPS" : "ms"), + frames); + } + return String.format("%-25s : %.3f ± %.3f %s", + name, scores[SCORE_MAIN], scores[SCORE_ERROR], (USE_FPS ? "FPS" : "ms")); + } + + protected double fps(long timeNs) { + return 1e9 / timeNs; + } + + protected double millis(long timeNs) { + return 1e-6 * timeNs; + } + + protected static long[] getPercentiles(final long[] data, final int first, final int last) { + final long[] pcts = new long[PCT_100 + 1]; + pcts[PCT_00] = data[first]; // 0% (min) + pcts[PCT_10] = data[pctIndex(first, last, 0.10)]; // 10% + pcts[PCT_25] = data[pctIndex(first, last, 0.25)]; // 25% (Q1) + pcts[PCT_50] = data[pctIndex(first, last, 0.50)]; // 50% (Median) + pcts[PCT_75] = data[pctIndex(first, last, 0.75)]; // 75% (Q3) + pcts[PCT_90] = data[pctIndex(first, last, 0.90)]; // 90% + pcts[PCT_100] = data[pctIndex(first, last, 1.00)]; // 100% (max) + return pcts; + } + + protected static int pctIndex(final int min, final int last, final double pct) { + return min + (int) Math.round((last - min) * pct); + } + } + + final static class PerfMeterRobot extends PerfMeterExecutor { + + private int nRobotTimes = 0; + private long[] robotTime = (fh.calibrate) ? new long[COUNT] : null; + + private int nDelayTimes = 0; + private long[] delayTime = (ROBOT_TIME_DELAY) ? null : new long[COUNT]; + + private long lastPaintTime = 0L; + private int renderedMarkerIdx = -1; + + private Robot robot = null; + + PerfMeterRobot(final FrameHandler fh) { + super(true, fh); + } + + protected void beforeExec() { + try { + robot = new Robot(); + } catch (AWTException ae) { + throw new RuntimeException(ae); + } + } + + protected void reset() { + super.reset(); + nRobotTimes = 0; + nDelayTimes = 0; + lastPaintTime = 0L; + renderedMarkerIdx = -1; + } + + protected void paintPanel(final Renderable renderable, final Graphics g) { + final int idx = markerIdx.get(); + final long start = System.nanoTime(); + + final Graphics2D g2d = (Graphics2D) g.create(); + try { + paintTest(renderable, g2d, MARKER[idx], false); + } finally { + g2d.dispose(); + } + + // Update paintIdx: + paintIdx.incrementAndGet(); + + // publish start time: + if (idx != renderedMarkerIdx) { + renderedMarkerIdx = idx; + markerStartTime.set(start); + } + } + + protected long getElapsedTime(final long paintTime) { + final long now = System.nanoTime(); + long duration = (!ROBOT_TIME_DELAY) ? roundDuration(now - paintTime) : 0L; + if (lastPaintTime != 0L) { + final long delay = roundDuration(now - lastPaintTime); + if (ROBOT_TIME_DELAY) { + duration = delay; + } else if (nDelayTimes < COUNT) { + delayTime[nDelayTimes++] = delay; + } + } + lastPaintTime = now; + return duration; + } + + private static long roundDuration(final long durationNs) { + return (durationNs <= 0L) ? 0L : ( + (ROBOT_TIME_ROUND) ? + FRAME_PREC_IN_NANOS * (long) Math.rint(((double) durationNs) / FRAME_PREC_IN_NANOS) : durationNs + ); + } + + protected Color getMarkerColor() { + final Point frameOffset = fh.frame.getLocationOnScreen(); + final Insets insets = fh.frame.getInsets(); + final int px = frameOffset.x + insets.left + BW / 2; + final int py = frameOffset.y + insets.top + BH / 2; + + final long beforeRobot = (fh.calibrate) ? System.nanoTime() : 0L; + + final Color c = robot.getPixelColor(px, py); + + if ((fh.calibrate) && (nRobotTimes < COUNT)) { + robotTime[nRobotTimes++] = System.nanoTime() - beforeRobot; + } + return c; + } + + protected String getResults() { + if (fh.calibrate && (nRobotTimes != 0)) { + fh.calibrate = false; // only first time + + Arrays.sort(robotTime); + + final long[] pcts = getPercentiles(robotTime, 0, nRobotTimes - 1); + + // free testTime to avoid any future usage: + testTime = null; + + System.err.printf("%-25s : %.3f ms (p00: %.3f p10: %.3f p25: %.3f p50: %.3f p75: %.3f p90: %.3f p100: %.3f ms) (%d times)%n", + "Robot" + (isMultiThreads() ? ("-" + fh.threadId) : ""), millis(pcts[PCT_50]), + millis(pcts[PCT_00]), millis(pcts[PCT_10]), millis(pcts[PCT_25]), millis(pcts[PCT_50]), + millis(pcts[PCT_75]), millis(pcts[PCT_90]), millis(pcts[PCT_100]), nRobotTimes); + } + if (nDelayTimes != 0) { + Arrays.sort(delayTime); + + final long[] pcts = getPercentiles(robotTime, 0, nDelayTimes - 1); + + // free delayTime to avoid any future usage: + delayTime = null; + + System.err.printf("%-25s : %.3f ms [%.3f FPS] (p00: %.3f p10: %.3f p25: %.3f p50: %.3f p75: %.3f p90: %.3f p100: %.3f ms) (%d times)%n", + "DelayTime-" + name + (isMultiThreads() ? ("-" + fh.threadId) : ""), millis(pcts[PCT_50]), fps(pcts[PCT_50]), + millis(pcts[PCT_00]), millis(pcts[PCT_10]), millis(pcts[PCT_25]), millis(pcts[PCT_50]), + millis(pcts[PCT_75]), millis(pcts[PCT_90]), millis(pcts[PCT_100]), nDelayTimes); + } + return super.getResults(); + } + } + + final static class PerfMeterImageProvider extends PerfMeterExecutor { + private final ImageProvider imageProvider; + + PerfMeterImageProvider(final FrameHandler fh) { + super(false, fh); + this.imageProvider = fh.imageProvider; + } + + protected void beforeExec() { + imageProvider.create(fh.frame.getGraphicsConfiguration(), IMAGE_W, IMAGE_H); + } + + protected void afterExec() { + imageProvider.reset(); + } + + protected void paintPanel(final Renderable renderable, final Graphics g) { + // suppose image provider is ready yet + final int idx = markerIdx.get(); + long start = System.nanoTime(); + + // Get Graphics from image provider: + final Graphics2D g2d = imageProvider.createGraphics(); + try { + paintTest(renderable, g2d, MARKER[idx], true); + } finally { + g2d.dispose(); + } + + final long now = System.nanoTime(); + + // Update paintIdx: + paintIdx.incrementAndGet(); + + // publish start time: + markerStartTime.set(start); + // publish elapsed time: + markerPaintTime.set(now - start); + + // Draw image on screen: + g.drawImage(imageProvider.getImage(), 0, 0, null); + } + + protected long getElapsedTime(long paintTime) { + return paintTime; + } + + protected Color getMarkerColor() { + final int px = BW / 2; + final int py = BH / 2; + + return new Color(imageProvider.getSnapshot().getRGB(px, py)); + } + } + + private final static class ImageProvider { + private final static int TRANSPARENCY = Transparency.TRANSLUCENT; + + private final boolean useVolatile; + private Image image = null; + + private ImageProvider(boolean useVolatile) { + this.useVolatile = useVolatile; + } + + void create(GraphicsConfiguration gc, int width, int height) { + this.image = (useVolatile) ? gc.createCompatibleVolatileImage(width, height, TRANSPARENCY) + : gc.createCompatibleImage(width, height, TRANSPARENCY); + } + + public void reset() { + image = null; + } + + public Image getImage() { + return image; + } + + public Graphics2D createGraphics() { + return (useVolatile) ? ((VolatileImage) image).createGraphics() + : ((BufferedImage) image).createGraphics(); + } + + public BufferedImage getSnapshot() { + return (useVolatile) ? ((VolatileImage) image).getSnapshot() + : (BufferedImage) image; + } + } + + private final FrameHandler fh; + + private final Particles balls = new Particles(N, R, BW, BH, WIDTH, HEIGHT); + + private final ParticleRenderer calibRenderer = new CalibrationParticleRenderer(); + private final ParticleRenderer flatRenderer = new FlatParticleRenderer(N, R); + private final ParticleRenderer clipFlatRenderer = new ClipFlatParticleRenderer(N, R); + private final ParticleRenderer flatOvalRotRenderer = new FlatOvalRotParticleRenderer(N, R); + private final ParticleRenderer flatBoxRenderer = new FlatBoxParticleRenderer(N, R); + private final ParticleRenderer clipFlatBoxParticleRenderer = new ClipFlatBoxParticleRenderer(N, R); + private final ParticleRenderer flatBoxRotRenderer = new FlatBoxRotParticleRenderer(N, R); + private final ParticleRenderer linGradOvalRotRenderer = new LinGradOvalRotParticleRenderer(N, R); + private final ParticleRenderer linGrad3OvalRotRenderer = new LinGrad3OvalRotParticleRenderer(N, R); + private final ParticleRenderer radGrad3OvalRotRenderer = new RadGrad3OvalRotParticleRenderer(N, R); + private final ParticleRenderer wiredRenderer = new WiredParticleRenderer(N, R); + private final ParticleRenderer wiredBoxRenderer = new WiredBoxParticleRenderer(N, R); + private final ParticleRenderer segRenderer = new SegParticleRenderer(N, R); + private final ParticleRenderer flatQuadRenderer = new FlatQuadParticleRenderer(N, R); + private final ParticleRenderer wiredQuadRenderer = new WiredQuadParticleRenderer(N, R); + private final ParticleRenderer imgRenderer = new ImgParticleRenderer(N, R); + private final ParticleRenderer volImgRenderer = new VolImgParticleRenderer(N, R); + private final ParticleRenderer textRenderer = new TextParticleRenderer(N, R); + private final ParticleRenderer largeTextRenderer = new LargeTextParticleRenderer(N, R); + private final ParticleRenderer whiteTextRenderer = new WhiteTextParticleRenderer(R); + private final ParticleRenderer argbSwBlitImageRenderer = new SwBlitImageParticleRenderer(N, R, BufferedImage.TYPE_INT_ARGB); + private final ParticleRenderer bgrSwBlitImageRenderer = new SwBlitImageParticleRenderer(N, R, BufferedImage.TYPE_INT_BGR); + private final ParticleRenderer argbSurfaceBlitImageRenderer = new SurfaceBlitImageParticleRenderer(N, R, BufferedImage.TYPE_INT_ARGB); + private final ParticleRenderer bgrSurfaceBlitImageRenderer = new SurfaceBlitImageParticleRenderer(N, R, BufferedImage.TYPE_INT_BGR); + + private final ParticleRenderer textWiredQuadBatchedRenderer = new BatchedParticleRenderer(textRenderer, wiredQuadRenderer); + private final ParticleRenderer textWiredQuadMixedRenderer = new MixedParticleRenderer(textRenderer, wiredQuadRenderer); + + private final ParticleRenderer volImgFlatBoxBatchedRenderer = new BatchedParticleRenderer(volImgRenderer, flatBoxRenderer); + private final ParticleRenderer volImgFlatBoxMixedRenderer = new MixedParticleRenderer(volImgRenderer, flatBoxRenderer); + + private final ParticleRenderer volImgWiredQuadBatchedRenderer = new BatchedParticleRenderer(volImgRenderer, wiredQuadRenderer); + private final ParticleRenderer volImgWiredQuadMixedRenderer = new MixedParticleRenderer(volImgRenderer, wiredQuadRenderer); + + private final ParticleRenderer volImgTextBatchedRenderer = new BatchedParticleRenderer(volImgRenderer, textRenderer); + private final ParticleRenderer volImgTextMixedRenderer = new MixedParticleRenderer(volImgRenderer, textRenderer); + + private final static Configurable AA = new ConfigurableAA(); + private final static Configurable TextAA = new ConfigurableTextAA(); + private final static Configurable TextLCD = new ConfigurableTextLCD(); + private final static Configurable XORMode = new ConfigurableXORMode(); + private final static Configurable XORModeLCDText = new ConfigurableXORModeTextLCD(); + + RenderPerfTest(final GraphicsConfiguration gc) { + fh = new FrameHandler(gc); + } + + ParticleRenderable createPR(final ParticleRenderer renderer) { + return new ParticleRenderable(balls, renderer); + } + + PerfMeter createPerfMeter(final String name) { + return new PerfMeter(fh, name); + } + + public void testCalibration() throws Exception { + createPerfMeter(testName).exec(createPR(calibRenderer)); + } + + public void testFlatOval() throws Exception { + createPerfMeter(testName).exec(createPR(flatRenderer)); + } + + public void testFlatOvalAA() throws Exception { + createPerfMeter(testName).exec(createPR(flatRenderer).configure(AA)); + } + + public void testClipFlatOval() throws Exception { + createPerfMeter(testName).exec(createPR(clipFlatRenderer)); + } + + public void testClipFlatOvalAA() throws Exception { + createPerfMeter(testName).exec(createPR(clipFlatRenderer).configure(AA)); + } + + public void testFlatBox() throws Exception { + createPerfMeter(testName).exec(createPR(flatBoxRenderer)); + } + + public void testFlatBoxAA() throws Exception { + createPerfMeter(testName).exec(createPR(flatBoxRenderer).configure(AA)); + } + + public void testClipFlatBox() throws Exception { + createPerfMeter(testName).exec(createPR(clipFlatBoxParticleRenderer)); + } + + public void testClipFlatBoxAA() throws Exception { + createPerfMeter(testName).exec(createPR(clipFlatBoxParticleRenderer).configure(AA)); + } + + public void testImage() throws Exception { + createPerfMeter(testName).exec(createPR(imgRenderer)); + } + + public void testImageAA() throws Exception { + createPerfMeter(testName).exec(createPR(imgRenderer).configure(AA)); + } + + public void testVolImage() throws Exception { + createPerfMeter(testName).exec(createPR(volImgRenderer)); + } + + public void testVolImageAA() throws Exception { + createPerfMeter(testName).exec(createPR(volImgRenderer).configure(AA)); + } + + public void testRotatedBox() throws Exception { + createPerfMeter(testName).exec(createPR(flatBoxRotRenderer)); + } + + public void testRotatedBoxAA() throws Exception { + createPerfMeter(testName).exec(createPR(flatBoxRotRenderer).configure(AA)); + } + + public void testRotatedOval() throws Exception { + createPerfMeter(testName).exec(createPR(flatOvalRotRenderer)); + } + + public void testRotatedOvalAA() throws Exception { + createPerfMeter(testName).exec(createPR(flatOvalRotRenderer).configure(AA)); + } + + public void testLinGrad3RotatedOval() throws Exception { + createPerfMeter(testName).exec(createPR(linGrad3OvalRotRenderer)); + } + + public void testLinGrad3RotatedOvalAA() throws Exception { + createPerfMeter(testName).exec(createPR(linGrad3OvalRotRenderer).configure(AA)); + } + + public void testRadGrad3RotatedOval() throws Exception { + createPerfMeter(testName).exec(createPR(radGrad3OvalRotRenderer)); + } + + public void testRadGrad3RotatedOvalAA() throws Exception { + createPerfMeter(testName).exec(createPR(radGrad3OvalRotRenderer).configure(AA)); + } + + public void testLinGradRotatedOval() throws Exception { + createPerfMeter(testName).exec(createPR(linGradOvalRotRenderer)); + } + + public void testLinGradRotatedOvalAA() throws Exception { + createPerfMeter(testName).exec(createPR(linGradOvalRotRenderer).configure(AA)); } public void testWiredBubbles() throws Exception { - (new PerfMeter("WiredBubbles")).exec(createPR(wiredRenderer)).report(); + createPerfMeter(testName).exec(createPR(wiredRenderer)); } public void testWiredBubblesAA() throws Exception { - (new PerfMeter("WiredBubblesAA")).exec(createPR(wiredRenderer).configure(AA)).report(); + createPerfMeter(testName).exec(createPR(wiredRenderer).configure(AA)); } public void testWiredBox() throws Exception { - (new PerfMeter("WiredBox")).exec(createPR(wiredBoxRenderer)).report(); + createPerfMeter(testName).exec(createPR(wiredBoxRenderer)); } public void testWiredBoxAA() throws Exception { - (new PerfMeter("WiredBoxAA")).exec(createPR(wiredBoxRenderer).configure(AA)).report(); + createPerfMeter(testName).exec(createPR(wiredBoxRenderer).configure(AA)); } public void testLines() throws Exception { - (new PerfMeter("Lines")).exec(createPR(segRenderer)).report(); + createPerfMeter(testName).exec(createPR(segRenderer)); } public void testLinesAA() throws Exception { - (new PerfMeter("LinesAA")).exec(createPR(segRenderer).configure(AA)).report(); + createPerfMeter(testName).exec(createPR(segRenderer).configure(AA)); } public void testFlatQuad() throws Exception { - (new PerfMeter("FlatQuad")).exec(createPR(flatQuadRenderer)).report(); + createPerfMeter(testName).exec(createPR(flatQuadRenderer)); } public void testFlatQuadAA() throws Exception { - (new PerfMeter("FlatQuadAA")).exec(createPR(flatQuadRenderer).configure(AA)).report(); + createPerfMeter(testName).exec(createPR(flatQuadRenderer).configure(AA)); } public void testWiredQuad() throws Exception { - (new PerfMeter("WiredQuad")).exec(createPR(wiredQuadRenderer)).report(); + createPerfMeter(testName).exec(createPR(wiredQuadRenderer)); } public void testWiredQuadAA() throws Exception { - (new PerfMeter("WiredQuadAA")).exec(createPR(wiredQuadRenderer).configure(AA)).report(); + createPerfMeter(testName).exec(createPR(wiredQuadRenderer).configure(AA)); } public void testTextNoAA() throws Exception { - (new PerfMeter("TextNoAA")).exec(createPR(textRenderer)).report(); + createPerfMeter(testName).exec(createPR(textRenderer)); } public void testTextLCD() throws Exception { - (new PerfMeter("TextLCD")).exec(createPR(textRenderer).configure(TextLCD)).report(); + createPerfMeter(testName).exec(createPR(textRenderer).configure(TextLCD)); } public void testTextGray() throws Exception { - (new PerfMeter("TextGray")).exec(createPR(textRenderer).configure(TextAA)).report(); + createPerfMeter(testName).exec(createPR(textRenderer).configure(TextAA)); } public void testLargeTextNoAA() throws Exception { - (new PerfMeter("LargeTextNoAA")).exec(createPR(largeTextRenderer)).report(); + createPerfMeter(testName).exec(createPR(largeTextRenderer)); } public void testLargeTextLCD() throws Exception { - (new PerfMeter("LargeTextLCD")).exec(createPR(largeTextRenderer).configure(TextLCD)).report(); + createPerfMeter(testName).exec(createPR(largeTextRenderer).configure(TextLCD)); } public void testLargeTextGray() throws Exception { - (new PerfMeter("LargeTextGray")).exec(createPR(largeTextRenderer).configure(TextAA)).report(); + createPerfMeter(testName).exec(createPR(largeTextRenderer).configure(TextAA)); } + public void testWhiteTextNoAA() throws Exception { - (new PerfMeter("WhiteTextNoAA")).exec(createPR(whiteTextRenderer)).report(); + createPerfMeter(testName).exec(createPR(whiteTextRenderer)); } public void testWhiteTextLCD() throws Exception { - (new PerfMeter("WhiteTextLCD")).exec(createPR(whiteTextRenderer).configure(TextLCD)).report(); + createPerfMeter(testName).exec(createPR(whiteTextRenderer).configure(TextLCD)); } public void testWhiteTextGray() throws Exception { - (new PerfMeter("WhiteTextGray")).exec(createPR(whiteTextRenderer).configure(TextAA)).report(); + createPerfMeter(testName).exec(createPR(whiteTextRenderer).configure(TextAA)); } public void testArgbSwBlitImage() throws Exception { - (new PerfMeter("ArgbSwBlitImage")).exec(createPR(argbSwBlitImageRenderer)).report(); + createPerfMeter(testName).exec(createPR(argbSwBlitImageRenderer)); } public void testBgrSwBlitImage() throws Exception { - (new PerfMeter("BgrSwBlitImage")).exec(createPR(bgrSwBlitImageRenderer)).report(); + createPerfMeter(testName).exec(createPR(bgrSwBlitImageRenderer)); } public void testArgbSurfaceBlitImage() throws Exception { - (new PerfMeter("ArgbSurfaceBlitImageRenderer")).exec(createPR(argbSurfaceBlitImageRenderer)).report(); + createPerfMeter(testName).exec(createPR(argbSurfaceBlitImageRenderer)); } public void testBgrSurfaceBlitImage() throws Exception { - (new PerfMeter("BgrSurfaceBlitImage")).exec(createPR(bgrSurfaceBlitImageRenderer)).report(); + createPerfMeter(testName).exec(createPR(bgrSurfaceBlitImageRenderer)); } + // XOR mode: public void testFlatOval_XOR() throws Exception { - (new PerfMeter("FlatOval_XOR")).exec(createPR(flatRenderer).configure(XORMode)).report(); + createPerfMeter(testName).exec(createPR(flatRenderer).configure(XORMode)); } public void testRotatedBox_XOR() throws Exception { - (new PerfMeter("RotatedBox_XOR")).exec(createPR(flatBoxRotRenderer).configure(XORMode)).report(); + createPerfMeter(testName).exec(createPR(flatBoxRotRenderer).configure(XORMode)); } public void testLines_XOR() throws Exception { - (new PerfMeter("Lines_XOR")).exec(createPR(segRenderer).configure(XORMode)).report(); + createPerfMeter(testName).exec(createPR(segRenderer).configure(XORMode)); } public void testImage_XOR() throws Exception { - (new PerfMeter("Image_XOR")).exec(createPR(imgRenderer).configure(XORMode)).report(); + createPerfMeter(testName).exec(createPR(imgRenderer).configure(XORMode)); } public void testTextNoAA_XOR() throws Exception { - (new PerfMeter("TextNoAA_XOR")).exec(createPR(textRenderer).configure(XORMode)).report(); + createPerfMeter(testName).exec(createPR(textRenderer).configure(XORMode)); } public void testTextLCD_XOR() throws Exception { - (new PerfMeter("TextLCD_XOR")).exec(createPR(textRenderer).configure(XORModeLCDText)).report(); + createPerfMeter(testName).exec(createPR(textRenderer).configure(XORModeLCDText)); + } + + // Mixed/Batched mode: + public void testTextWiredQuadBat() throws Exception { + createPerfMeter(testName).exec(createPR(textWiredQuadBatchedRenderer)); + } + + public void testTextWiredQuadMix() throws Exception { + createPerfMeter(testName).exec(createPR(textWiredQuadMixedRenderer)); + } + + public void testTextWiredQuadAABat() throws Exception { + createPerfMeter(testName).exec(createPR(textWiredQuadBatchedRenderer).configure(AA)); + } + + public void testTextWiredQuadAAMix() throws Exception { + createPerfMeter(testName).exec(createPR(textWiredQuadMixedRenderer).configure(AA)); + } + + public void testVolImageFlatBoxBat() throws Exception { + createPerfMeter(testName).exec(createPR(volImgFlatBoxBatchedRenderer)); + } + + public void testVolImageFlatBoxMix() throws Exception { + createPerfMeter(testName).exec(createPR(volImgFlatBoxMixedRenderer)); + } + + public void testVolImageFlatBoxAABat() throws Exception { + createPerfMeter(testName).exec(createPR(volImgFlatBoxBatchedRenderer).configure(AA)); + } + + public void testVolImageFlatBoxAAMix() throws Exception { + createPerfMeter(testName).exec(createPR(volImgFlatBoxMixedRenderer).configure(AA)); + } + + public void testVolImageWiredQuadBat() throws Exception { + createPerfMeter(testName).exec(createPR(volImgWiredQuadBatchedRenderer)); + } + + public void testVolImageWiredQuadMix() throws Exception { + createPerfMeter(testName).exec(createPR(volImgWiredQuadMixedRenderer)); + } + + public void testVolImageWiredQuadAABat() throws Exception { + createPerfMeter(testName).exec(createPR(volImgWiredQuadBatchedRenderer).configure(AA)); + } + + public void testVolImageWiredQuadAAMix() throws Exception { + createPerfMeter(testName).exec(createPR(volImgWiredQuadMixedRenderer).configure(AA)); + } + + public void testVolImageTextNoAABat() throws Exception { + createPerfMeter(testName).exec(createPR(volImgTextBatchedRenderer)); + } + + public void testVolImageTextNoAAMix() throws Exception { + createPerfMeter(testName).exec(createPR(volImgTextMixedRenderer)); + } + + private static void help() { + System.out.print("##############################################################\n"); + System.out.printf("# %s\n", VERSION); + System.out.print("##############################################################\n"); + System.out.println("# java ... RenderPerfTest "); + System.out.println("#"); + System.out.println("# Supported arguments :"); + System.out.println("# -h : display this help"); + System.out.println("# -v : set verbose output"); + System.out.println("#"); + System.out.println("# -e= : set the execution mode (default: " + EXEC_MODE_DEFAULT + + ") among " + EXEC_MODES); + System.out.println("#"); + System.out.println("# -f : use FPS unit (default)"); + System.out.println("# -t : use TIME(ms) unit"); + System.out.println("#"); + System.out.println("# -g=all|\"0-0,0-1...\" : use 'all' or specific graphics configurations"); + System.out.println("#"); + System.out.println("# -font=\"\" : set the font name used by all text renderers"); + System.out.println("# -fontSize= : set the font size used by Text renderers"); + System.out.println("# -fontSizeLarge= : set the font size used by LargeText renderers"); + System.out.println("# -text=\"\" : set the text drawn by all text renderers"); + System.out.println("#"); + System.out.println("# -lf : list available font names"); + System.out.println("# -lg : list available graphics configurations"); + System.out.println("#"); + System.out.println("# -n= : set number of primitives (default: " + N_DEFAULT + ")"); + System.out.println("# -r= : set number of test repeats (default: 1)"); + System.out.println("#"); + System.out.println("#"); + System.out.println("# -w= : use number of test frames (default: 1) per screen"); + System.out.println("#"); + System.out.println("# -u= : set number of warmup iterations (default: " + MIN_COUNT + ")"); + System.out.println("#"); + + System.out.print("# Supported test arguments :"); + + final ArrayList testCases = new ArrayList<>(); + for (Method m : RenderPerfTest.class.getDeclaredMethods()) { + if (m.getName().startsWith("test") && !ignoredTests.contains(m.getName())) { + testCases.add(m); + } + } + testCases.sort(Comparator.comparing(Method::getName)); + for (Method m : testCases) { + System.out.print(extractTestName(m)); + System.out.print(" "); + } + System.out.println(); + } + + private static String extractTestName(final Method m) { + return m.getName().substring("test".length()); } public static void main(String[] args) - throws InvocationTargetException, IllegalAccessException, NoSuchMethodException - { - RenderPerfTest test = new RenderPerfTest(); - - if (args.length > 0) { - for (String testCase : args) { - Method m = RenderPerfTest.class.getDeclaredMethod("test" + testCase); - m.invoke(test); - } - } else { - Method[] methods = RenderPerfTest.class.getDeclaredMethods(); - for (Method m : methods) { + throws NoSuchMethodException, NumberFormatException { + // Set the default locale to en-US locale (for Numerical Fields "." ",") + Locale.setDefault(Locale.US); + + boolean help = false; + final ArrayList testCases = new ArrayList<>(); + + for (String arg : args) { + if (arg.length() >= 2) { + if (arg.startsWith("-")) { + switch (arg.substring(1, 2)) { + case "e": + if (arg.length() >= 3) { + EXEC_MODE = arg.substring(3).toLowerCase(); + } + break; + case "f": + if (arg.length() == 2) { + USE_FPS = true; + } else { + if ((arg.length() > 6) && "font=".equalsIgnoreCase(arg.substring(1, 6))) { + TEXT_FONT = arg.substring(6); + break; + } + if ((arg.length() > 10) && "fontSize=".equalsIgnoreCase(arg.substring(1, 10))) { + TEXT_SIZE_DEFAULT = Integer.parseInt(arg.substring(10)); + break; + } + if ((arg.length() > 15) && "fontSizeLarge=".equalsIgnoreCase(arg.substring(1, 15))) { + TEXT_SIZE_LARGE = Integer.parseInt(arg.substring(15)); + break; + } + } + break; + case "g": + if (arg.length() >= 3) { + GC_MODE = arg.substring(3).toLowerCase(); + } + break; + case "h": + help = true; + break; + case "l": + if (arg.length() == 3) { + if ("f".equalsIgnoreCase(arg.substring(2, 3))) { + VERBOSE_FONT_CONFIG = true; + } else if ("g".equalsIgnoreCase(arg.substring(2, 3))) { + VERBOSE_GRAPHICS_CONFIG = true; + } + } + break; + case "t": + if (arg.length() == 2) { + USE_FPS = false; + } else { + if ((arg.length() > 6) && "text=".equalsIgnoreCase(arg.substring(1, 6))) { + TEXT_STR = arg.substring(6); + } + } + break; + case "n": + if (arg.length() >= 3) { + N = Integer.parseInt(arg.substring(3)); + } + break; + case "r": + if (arg.length() >= 3) { + REPEATS = Integer.parseInt(arg.substring(3)); + } + break; + case "v": + VERBOSE = true; + break; + case "w": + if (arg.length() >= 3) { + NW = Integer.parseInt(arg.substring(3)); + } + break; + case "u": + if (arg.length() >= 3) { + WARMUP_COUNT = Integer.parseInt(arg.substring(3)); + } + break; + default: + System.err.println("Unsupported argument '" + arg + "' !"); + help = true; + } + } else { + Method m = RenderPerfTest.class.getDeclaredMethod("test" + arg); + testCases.add(m); + } + } + } + if (testCases.isEmpty()) { + for (Method m : RenderPerfTest.class.getDeclaredMethods()) { if (m.getName().startsWith("test") && !ignoredTests.contains(m.getName())) { - m.invoke(test); + testCases.add(m); } } + testCases.sort(Comparator.comparing(Method::getName)); + } + + if (help) { + help(); + } + + if (CALIBRATION) { + Method m = RenderPerfTest.class.getDeclaredMethod("testCalibration"); + testCases.add(0, m); // first + } + + if (VERBOSE) { + System.out.print("##############################################################\n"); + System.out.printf("# %s\n", VERSION); + System.out.print("##############################################################\n"); + System.out.printf("# Java: %s\n", System.getProperty("java.runtime.version")); + System.out.printf("# VM: %s %s (%s)\n", System.getProperty("java.vm.name"), System.getProperty("java.vm.version"), System.getProperty("java.vm.info")); + System.out.printf("# OS: %s %s (%s)\n", System.getProperty("os.name"), System.getProperty("os.version"), System.getProperty("os.arch")); + System.out.printf("# CPUs: %d (virtual)\n", Runtime.getRuntime().availableProcessors()); + System.out.print("##############################################################\n"); + System.out.printf("# AWT Toolkit: %s \n", TOOLKIT.getClass().getSimpleName()); + System.out.printf("# Execution mode: %s\n", EXEC_MODE); + System.out.printf("# GraphicsConfiguration mode: %s\n", GC_MODE); + System.out.print("##############################################################\n"); + System.out.printf("# Repeats: %d\n", REPEATS); + System.out.printf("# NW: %d\n", NW); + System.out.printf("# N: %d\n", N); + System.out.printf("# WARMUP_COUNT: %d\n", WARMUP_COUNT); + System.out.printf("# Unit: %s\n", USE_FPS ? "FPS" : "TIME(ms)"); + System.out.print("##############################################################\n"); + System.out.printf("# Font: '%s'\n", TEXT_FONT); + System.out.printf("# Text: '%s'\n", TEXT_STR); + System.out.printf("# FontSize: %s\n", TEXT_SIZE_DEFAULT); + System.out.printf("# FontSizeLarge: %s\n", TEXT_SIZE_LARGE); + System.out.print("##############################################################\n"); + } + + // Graphics Configuration handling: + final Set fontNames = new LinkedHashSet<>(); + final Map gcByID = new LinkedHashMap<>(); + final Map idByGC = new HashMap<>(); + + final GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment(); + + for (String name : ge.getAvailableFontFamilyNames()) { + fontNames.add(name); + } + // Check font: + if (!fontNames.contains(TEXT_FONT)) { + System.err.println("Bad font name: [" + TEXT_FONT + "] !"); + VERBOSE_FONT_CONFIG = true; } + + if (VERBOSE_FONT_CONFIG) { + System.out.print("# Available font names: "); + + for (String name : fontNames) { + System.out.print("'"); + System.out.print(name); + System.out.print("' "); + } + System.out.println(); + } + + final GraphicsDevice[] gds = ge.getScreenDevices(); + + if (VERBOSE_GRAPHICS_CONFIG) { + System.out.println("# Available GraphicsDevice(s) and their GraphicsConfiguration(s):"); + } + + for (int gdIdx = 0; gdIdx < gds.length; gdIdx++) { + final GraphicsDevice gd = gds[gdIdx]; + if (VERBOSE_GRAPHICS_CONFIG) { + System.out.println("# [" + gdIdx + "] = GraphicsDevice[" + gd.getIDstring() + "]"); + } + + final GraphicsConfiguration[] gcs = gd.getConfigurations(); + + for (int gcIdx = 0; gcIdx < gcs.length; gcIdx++) { + final GraphicsConfiguration gc = gcs[gcIdx]; + final String gcId = gdIdx + "-" + gcIdx; + gcByID.put(gcId, gc); + idByGC.put(gc, gcId); + if (VERBOSE_GRAPHICS_CONFIG) { + System.out.println("# - [" + gcId + "] = GraphicsConfiguration[" + gc + "] bounds:" + gc.getBounds()); + } + } + } + + final Set gcSet = new LinkedHashSet<>(); + + if (GC_MODE != null) { + if (!GC_MODE_DEF.equals(GC_MODE)) { + if (GC_MODE_ALL.equals(GC_MODE)) { + gcSet.addAll(gcByID.values()); + } else { + for (String gcKey : GC_MODE.split(",")) { + final GraphicsConfiguration gc = gcByID.get(gcKey); + if (gc != null) { + gcSet.add(gc); + } else { + System.err.println("Bad GraphicsConfiguration identifier 'x-y' where x is GraphicsDevice ID " + + "and y GraphicsConfiguration ID : [" + gcKey + "] ! (available values: " + gcByID.keySet() + ")"); + } + } + } + } + } + if (gcSet.isEmpty()) { + final GraphicsDevice gdDef = ge.getDefaultScreenDevice(); + final GraphicsConfiguration gcDef = gdDef.getDefaultConfiguration(); + final String gcId = idByGC.get(gcDef); + + if (VERBOSE_GRAPHICS_CONFIG) { + System.out.println("# Using default [" + gcId + "] = GraphicsConfiguration[" + gcDef + "] bounds:" + gcDef.getBounds()); + } + gcSet.add(gcDef); + } + + final List gcList = new ArrayList<>(gcSet); + final int NGC = gcList.size(); + + System.out.print("# Using GraphicsConfiguration(s): "); + for (GraphicsConfiguration gc : gcList) { + final String gcId = idByGC.get(gc); + System.out.print("[" + gcId + "][" + gc + "]"); + System.out.print(" "); + } + System.out.println(); + + final List instances = new ArrayList<>(); + int retCode = 0; + try { + if (!help) { + final List threads = new ArrayList<>(); + + for (int i = 0; i < NGC; i++) { + final GraphicsConfiguration gc = gcList.get(i); + + for (int j = 0; j < NW; j++) { + final RenderPerfTest rp = new RenderPerfTest(gc); + instances.add(rp); + threads.add(rp.createThreadTests(threads.size() + 1, j + 1, testCases)); + } + } + if (TRACE_SYNC) traceSync("testCount: " + testCount); + + initThreads(threads.size()); + initBarrierStart(); + + for (Thread thread : threads) { + if (TRACE_SYNC) traceSync(thread.getName() + " starting..."); + thread.start(); + } + + for (int n = 0; n < testCount; n++) { + if (VERBOSE) { + final int k = n / REPEATS; + final String methodName = extractTestName(testCases.get(k)); + System.out.println("# --- Test [" + (n + 1) + " / " + testCount + "] = " + methodName + " ---"); + } + + // reset stop barrier (to be ready): + initBarrierStop(); + + if (TRACE_SYNC) traceSync("Waiting " + threadCount + " threads to be ready..."); + readyCount.await(); + + if (TRACE_SYNC) traceSync("Threads are ready => starting benchmark on " + threadCount + " threads now"); + triggerStart.countDown(); + + // reset done barrier (to be ready): + initBarrierDone(); + + if (TRACE_SYNC) traceSync("Waiting " + threadCount + " threads to complete benchmark..."); + completedCount.await(); + + if (TRACE_SYNC) traceSync("Test completed on " + threadCount + " threads => stopping benchmark on all threads now"); + triggerStop.countDown(); + + // reset start barrier (to be ready): + initBarrierStart(); + + if (TRACE_SYNC) traceSync("Waiting " + threadCount + " threads to exit test..."); + doneCount.await(); + + if (TRACE_SYNC) traceSync("Test exited on " + threadCount + " threads => finalize benchmark on all threads now"); + triggerExit.countDown(); + } + + for (Thread thread : threads) { + thread.join(); + if (TRACE_SYNC) traceSync(thread.getName() + " terminated"); + } + } + } catch (Throwable th) { + System.err.println("Exception occurred during :"); + th.printStackTrace(System.err); + retCode = 1; + } finally { + for (RenderPerfTest rp : instances) { + try { + rp.fh.hideFrameAndWait(); + } catch (Throwable th) { + System.err.println("Exception occurred in hideFrameAndWait():"); + th.printStackTrace(System.err); + retCode = 1; + } + } + // ensure jvm immediate shutdown: + System.exit(retCode); + } + } + + // thread synchronization + + private static int threadCount = 0; + + private static int testCount = 0; + private static volatile String testName = null; + + private static volatile CountDownLatch readyCount = null; + private static volatile CountDownLatch triggerStart = null; + + private static volatile CountDownLatch completedCount = null; + private static volatile CountDownLatch triggerStop = null; + + private static volatile CountDownLatch doneCount = null; + private static volatile CountDownLatch triggerExit = null; + + static void traceSync(final String msg) { + System.out.println("[" + System.nanoTime() + "] " + msg); + } + + private static void initThreads(int count) { + threadCount = count; + if (TRACE_SYNC) traceSync("initThreads(): threadCount: " + threadCount); + } + + static boolean isMultiThreads() { + return threadCount > 1; + } + + private static void initBarrierStart() { + readyCount = new CountDownLatch(threadCount); + triggerStart = new CountDownLatch(1); + } + + private static void initBarrierStop() { + completedCount = new CountDownLatch(threadCount); + triggerStop = new CountDownLatch(1); + } + + private static void initBarrierDone() { + doneCount = new CountDownLatch(threadCount); + triggerExit = new CountDownLatch(1); + } + + public Thread createThreadTests(final int threadId, final int frameId, + final ArrayList testCases) throws Exception { + fh.setIds(threadId, frameId); + + SwingUtilities.invokeAndWait(new Runnable() { + @Override + public void run() { + fh.prepareFrameEDT(VERSION + " [" + fh.threadId + "]"); + + final JLabel label = new JLabel((DELAY_START) ? "Waiting 3s before starting benchmark..." : "Starting benchmark..."); + label.setForeground(Color.WHITE); + + final JPanel panel = new JPanel(); + panel.add(label); + + fh.showFrameEDT(panel); + } + }); + + // Wait frame to be shown: + fh.waitFrameShown(); + + // Set test count per thread: + testCount = testCases.size() * REPEATS; + + final RenderPerfTest rp = this; + return new Thread("RenderPerfThread[" + threadId + "]") { + @Override + public void run() { + if (DELAY_START) { + RenderPerfTest.sleep(3000); + } + try { + for (Method m : testCases) { + for (int i = 0; i < REPEATS; i++) { + testName = extractTestName(m); + m.invoke(rp); + } + } + } catch (Throwable th) { + System.err.println("Exception occurred in RenderPerfThread[" + threadId + "]:"); + th.printStackTrace(System.err); + } + } + }; + } + + private static void sleep(final long millis) { + if (millis > 0) { + try { + Thread.sleep(millis); + } catch (InterruptedException ie) { + ie.printStackTrace(System.err); + } + } + } + + /** regular expression used to match characters different than alpha/numeric/_/-/. (1..n) */ + private final static Pattern PATTERN_NON_FILE_NAME = Pattern.compile("[^a-zA-Z0-9\\-_\\.]"); + + private static String replaceNonFileNameChars(final String value) { + return PATTERN_NON_FILE_NAME.matcher(value).replaceAll("_"); } } From 0f4cd8f1c06a78cce8eea241034bcb33154f769e Mon Sep 17 00:00:00 2001 From: Albert Mingkun Yang Date: Thu, 22 Feb 2024 07:57:31 +0000 Subject: [PATCH 14/38] 8326414: Serial: Inline SerialHeap::create_rem_set Reviewed-by: kbarrett --- src/hotspot/share/gc/serial/serialHeap.cpp | 7 +------ src/hotspot/share/gc/serial/serialHeap.hpp | 1 - 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/src/hotspot/share/gc/serial/serialHeap.cpp b/src/hotspot/share/gc/serial/serialHeap.cpp index 61260ccd242c5..2fd5dc1889aea 100644 --- a/src/hotspot/share/gc/serial/serialHeap.cpp +++ b/src/hotspot/share/gc/serial/serialHeap.cpp @@ -191,7 +191,7 @@ jint SerialHeap::initialize() { ReservedSpace young_rs = heap_rs.first_part(MaxNewSize); ReservedSpace old_rs = heap_rs.last_part(MaxNewSize); - _rem_set = create_rem_set(heap_rs.region()); + _rem_set = new CardTableRS(heap_rs.region()); _rem_set->initialize(young_rs.base(), old_rs.base()); CardTableBarrierSet *bs = new CardTableBarrierSet(_rem_set); @@ -206,11 +206,6 @@ jint SerialHeap::initialize() { return JNI_OK; } - -CardTableRS* SerialHeap::create_rem_set(const MemRegion& reserved_region) { - return new CardTableRS(reserved_region); -} - ReservedHeapSpace SerialHeap::allocate(size_t alignment) { // Now figure out the total size. const size_t pageSize = UseLargePages ? os::large_page_size() : os::vm_page_size(); diff --git a/src/hotspot/share/gc/serial/serialHeap.hpp b/src/hotspot/share/gc/serial/serialHeap.hpp index 32c1f84389580..f1f2e5a1cc1a7 100644 --- a/src/hotspot/share/gc/serial/serialHeap.hpp +++ b/src/hotspot/share/gc/serial/serialHeap.hpp @@ -141,7 +141,6 @@ class SerialHeap : public CollectedHeap { public: // Returns JNI_OK on success jint initialize() override; - virtual CardTableRS* create_rem_set(const MemRegion& reserved_region); // Does operations required after initialization has been done. void post_initialize() override; From 10eafdc62e8216e6ef69773fe491a21346c8682d Mon Sep 17 00:00:00 2001 From: Axel Boldt-Christmas Date: Thu, 22 Feb 2024 09:14:20 +0000 Subject: [PATCH 15/38] 8325870: Zap end padding bits for ArrayOops in non-release builds Reviewed-by: stefank, ayang --- src/hotspot/share/gc/shared/memAllocator.cpp | 22 +++++++++++++- src/hotspot/share/gc/shared/memAllocator.hpp | 4 ++- src/hotspot/share/gc/z/zObjArrayAllocator.cpp | 4 ++- .../share/utilities/globalDefinitions.hpp | 29 ++++++++++--------- 4 files changed, 42 insertions(+), 17 deletions(-) diff --git a/src/hotspot/share/gc/shared/memAllocator.cpp b/src/hotspot/share/gc/shared/memAllocator.cpp index e4b91413537bd..dc94b83c13998 100644 --- a/src/hotspot/share/gc/shared/memAllocator.cpp +++ b/src/hotspot/share/gc/shared/memAllocator.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2024, Oracle and/or its affiliates. All rights reserved. * 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,6 +41,7 @@ #include "services/lowMemoryDetector.hpp" #include "utilities/align.hpp" #include "utilities/copy.hpp" +#include "utilities/globalDefinitions.hpp" class MemAllocator::Allocation: StackObj { friend class MemAllocator; @@ -408,11 +409,30 @@ oop ObjArrayAllocator::initialize(HeapWord* mem) const { assert(_length >= 0, "length should be non-negative"); if (_do_zero) { mem_clear(mem); + mem_zap_end_padding(mem); } arrayOopDesc::set_length(mem, _length); return finish(mem); } +#ifndef PRODUCT +void ObjArrayAllocator::mem_zap_end_padding(HeapWord* mem) const { + const size_t length_in_bytes = static_cast(_length) << ArrayKlass::cast(_klass)->log2_element_size(); + const BasicType element_type = ArrayKlass::cast(_klass)->element_type(); + const size_t base_offset_in_bytes = arrayOopDesc::base_offset_in_bytes(element_type); + const size_t size_in_bytes = _word_size * BytesPerWord; + + const address obj_end = reinterpret_cast

(mem) + size_in_bytes; + const address base = reinterpret_cast
(mem) + base_offset_in_bytes; + const address elements_end = base + length_in_bytes; + assert(elements_end <= obj_end, "payload must fit in object"); + if (elements_end < obj_end) { + const size_t padding_in_bytes = obj_end - elements_end; + Copy::fill_to_bytes(elements_end, padding_in_bytes, heapPaddingByteVal); + } +} +#endif + oop ClassAllocator::initialize(HeapWord* mem) const { // Set oop_size field before setting the _klass field because a // non-null _klass field indicates that the object is parsable by diff --git a/src/hotspot/share/gc/shared/memAllocator.hpp b/src/hotspot/share/gc/shared/memAllocator.hpp index 93ed10dce0555..bd4ae04babfcd 100644 --- a/src/hotspot/share/gc/shared/memAllocator.hpp +++ b/src/hotspot/share/gc/shared/memAllocator.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -98,6 +98,8 @@ class ObjArrayAllocator: public MemAllocator { const int _length; const bool _do_zero; + void mem_zap_end_padding(HeapWord* mem) const PRODUCT_RETURN; + public: ObjArrayAllocator(Klass* klass, size_t word_size, int length, bool do_zero, Thread* thread = Thread::current()) diff --git a/src/hotspot/share/gc/z/zObjArrayAllocator.cpp b/src/hotspot/share/gc/z/zObjArrayAllocator.cpp index c65f0d613d068..faa1290f37e84 100644 --- a/src/hotspot/share/gc/z/zObjArrayAllocator.cpp +++ b/src/hotspot/share/gc/z/zObjArrayAllocator.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -132,6 +132,8 @@ oop ZObjArrayAllocator::initialize(HeapWord* mem) const { assert(result, "Array initialization should always succeed the second time"); } + mem_zap_end_padding(mem); + ZThreadLocalData::clear_invisible_root(_thread); // Signal to the ZIterator that this is no longer an invisible root diff --git a/src/hotspot/share/utilities/globalDefinitions.hpp b/src/hotspot/share/utilities/globalDefinitions.hpp index c0f5b71966662..ce94629c84bd4 100644 --- a/src/hotspot/share/utilities/globalDefinitions.hpp +++ b/src/hotspot/share/utilities/globalDefinitions.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -1028,19 +1028,20 @@ enum LockingMode { //---------------------------------------------------------------------------------------------------- // Special constants for debugging -const jint badInt = -3; // generic "bad int" value -const intptr_t badAddressVal = -2; // generic "bad address" value -const intptr_t badOopVal = -1; // generic "bad oop" value -const intptr_t badHeapOopVal = (intptr_t) CONST64(0x2BAD4B0BBAADBABE); // value used to zap heap after GC -const int badStackSegVal = 0xCA; // value used to zap stack segments -const int badHandleValue = 0xBC; // value used to zap vm handle area -const int badResourceValue = 0xAB; // value used to zap resource area -const int freeBlockPad = 0xBA; // value used to pad freed blocks. -const int uninitBlockPad = 0xF1; // value used to zap newly malloc'd blocks. -const juint uninitMetaWordVal= 0xf7f7f7f7; // value used to zap newly allocated metachunk -const juint badHeapWordVal = 0xBAADBABE; // value used to zap heap after GC -const juint badMetaWordVal = 0xBAADFADE; // value used to zap metadata heap after GC -const int badCodeHeapNewVal= 0xCC; // value used to zap Code heap at allocation +const jint badInt = -3; // generic "bad int" value +const intptr_t badAddressVal = -2; // generic "bad address" value +const intptr_t badOopVal = -1; // generic "bad oop" value +const intptr_t badHeapOopVal = (intptr_t) CONST64(0x2BAD4B0BBAADBABE); // value used to zap heap after GC +const int badStackSegVal = 0xCA; // value used to zap stack segments +const int badHandleValue = 0xBC; // value used to zap vm handle area +const int badResourceValue = 0xAB; // value used to zap resource area +const int freeBlockPad = 0xBA; // value used to pad freed blocks. +const int uninitBlockPad = 0xF1; // value used to zap newly malloc'd blocks. +const juint uninitMetaWordVal = 0xf7f7f7f7; // value used to zap newly allocated metachunk +const jubyte heapPaddingByteVal = 0xBD; // value used to zap object padding in the heap +const juint badHeapWordVal = 0xBAADBABE; // value used to zap heap after GC +const juint badMetaWordVal = 0xBAADFADE; // value used to zap metadata heap after GC +const int badCodeHeapNewVal = 0xCC; // value used to zap Code heap at allocation const int badCodeHeapFreeVal = 0xDD; // value used to zap Code heap at deallocation const intptr_t badDispHeaderDeopt = 0xDE0BD000; // value to fill unused displaced header during deoptimization const intptr_t badDispHeaderOSR = 0xDEAD05A0; // value to fill unused displaced header during OSR From cc1e216eb9e4c817f6744ec76d62f21f4bd14489 Mon Sep 17 00:00:00 2001 From: SendaoYan Date: Thu, 22 Feb 2024 09:59:37 +0000 Subject: [PATCH 16/38] 8326461: tools/jlink/CheckExecutable.java fails as .debuginfo files are not executable Reviewed-by: shade, alanb --- test/jdk/tools/jlink/CheckExecutable.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/jdk/tools/jlink/CheckExecutable.java b/test/jdk/tools/jlink/CheckExecutable.java index 4c9ee6a6c5101..d8449682b1895 100644 --- a/test/jdk/tools/jlink/CheckExecutable.java +++ b/test/jdk/tools/jlink/CheckExecutable.java @@ -40,7 +40,7 @@ public class CheckExecutable { // The bin directory may contain non-executable files (see 8132704) - private static final String IGNORE = "glob:{*.diz,jmc.ini}"; + private static final String IGNORE = "glob:{*.diz,jmc.ini,*.debuginfo}"; public static void main(String args[]) throws IOException { String JAVA_HOME = System.getProperty("java.home"); From 4406915ebce4266b3eb4a238382fff3c2c1d1739 Mon Sep 17 00:00:00 2001 From: Roland Westrelin Date: Thu, 22 Feb 2024 11:07:13 +0000 Subject: [PATCH 17/38] 8323274: C2: array load may float above range check Reviewed-by: epeter, thartmann --- src/hotspot/share/opto/arraycopynode.cpp | 5 +- src/hotspot/share/opto/loopnode.hpp | 2 + src/hotspot/share/opto/loopopts.cpp | 64 ++++++- src/hotspot/share/opto/split_if.cpp | 23 +++ ...ArrayAccessAboveRCAfterPartialPeeling.java | 130 ++++++++++++++ .../TestArrayAccessAboveRCAfterSinking.java | 132 ++++++++++++++ .../TestArrayAccessAboveRCAfterSplitIf.java | 161 ++++++++++++++++++ ...estArrayAccessAboveRCAfterUnswitching.java | 103 +++++++++++ ...estArrayAccessAboveRCForArrayCopyLoad.java | 56 ++++++ 9 files changed, 672 insertions(+), 4 deletions(-) create mode 100644 test/hotspot/jtreg/compiler/rangechecks/TestArrayAccessAboveRCAfterPartialPeeling.java create mode 100644 test/hotspot/jtreg/compiler/rangechecks/TestArrayAccessAboveRCAfterSinking.java create mode 100644 test/hotspot/jtreg/compiler/rangechecks/TestArrayAccessAboveRCAfterSplitIf.java create mode 100644 test/hotspot/jtreg/compiler/rangechecks/TestArrayAccessAboveRCAfterUnswitching.java create mode 100644 test/hotspot/jtreg/compiler/rangechecks/TestArrayAccessAboveRCForArrayCopyLoad.java diff --git a/src/hotspot/share/opto/arraycopynode.cpp b/src/hotspot/share/opto/arraycopynode.cpp index 71b7a7e5024d9..4e2b159bf8e14 100644 --- a/src/hotspot/share/opto/arraycopynode.cpp +++ b/src/hotspot/share/opto/arraycopynode.cpp @@ -152,7 +152,10 @@ int ArrayCopyNode::get_count(PhaseGVN *phase) const { } Node* ArrayCopyNode::load(BarrierSetC2* bs, PhaseGVN *phase, Node*& ctl, MergeMemNode* mem, Node* adr, const TypePtr* adr_type, const Type *type, BasicType bt) { - DecoratorSet decorators = C2_READ_ACCESS | C2_CONTROL_DEPENDENT_LOAD | IN_HEAP | C2_ARRAY_COPY; + // Pin the load: if this is an array load, it's going to be dependent on a condition that's not a range check for that + // access. If that condition is replaced by an identical dominating one, then an unpinned load would risk floating + // above runtime checks that guarantee it is within bounds. + DecoratorSet decorators = C2_READ_ACCESS | C2_CONTROL_DEPENDENT_LOAD | IN_HEAP | C2_ARRAY_COPY | C2_UNKNOWN_CONTROL_LOAD; C2AccessValuePtr addr(adr, adr_type); C2OptAccess access(*phase, ctl, mem, decorators, bt, adr->in(AddPNode::Base), addr); Node* res = bs->load_at(access, type); diff --git a/src/hotspot/share/opto/loopnode.hpp b/src/hotspot/share/opto/loopnode.hpp index b1a0d95ddf266..252c53b52c1e2 100644 --- a/src/hotspot/share/opto/loopnode.hpp +++ b/src/hotspot/share/opto/loopnode.hpp @@ -1744,6 +1744,8 @@ class PhaseIdealLoop : public PhaseTransform { void update_addp_chain_base(Node* x, Node* old_base, Node* new_base); bool can_move_to_inner_loop(Node* n, LoopNode* n_loop, Node* x); + + void pin_array_access_nodes_dependent_on(Node* ctrl); }; diff --git a/src/hotspot/share/opto/loopopts.cpp b/src/hotspot/share/opto/loopopts.cpp index c5d8ed39d9d0c..0010a7cd90353 100644 --- a/src/hotspot/share/opto/loopopts.cpp +++ b/src/hotspot/share/opto/loopopts.cpp @@ -1490,7 +1490,16 @@ void PhaseIdealLoop::split_if_with_blocks_post(Node *n) { // Replace the dominated test with an obvious true or false. // Place it on the IGVN worklist for later cleanup. C->set_major_progress(); - dominated_by(prevdom->as_IfProj(), n->as_If()); + // Split if: pin array accesses that are control dependent on a range check and moved to a regular if, + // to prevent an array load from floating above its range check. There are three cases: + // 1. Move from RangeCheck "a" to RangeCheck "b": don't need to pin. If we ever remove b, then we pin + // all its array accesses at that point. + // 2. We move from RangeCheck "a" to regular if "b": need to pin. If we ever remove b, then its array + // accesses would start to float, since we don't pin at that point. + // 3. If we move from regular if: don't pin. All array accesses are already assumed to be pinned. + bool pin_array_access_nodes = n->Opcode() == Op_RangeCheck && + prevdom->in(0)->Opcode() != Op_RangeCheck; + dominated_by(prevdom->as_IfProj(), n->as_If(), false, pin_array_access_nodes); DEBUG_ONLY( if (VerifyLoopOptimizations) { verify(); } ); return; } @@ -1664,7 +1673,20 @@ void PhaseIdealLoop::try_sink_out_of_loop(Node* n) { // n has a control input inside a loop but get_ctrl() is member of an outer loop. This could happen, for example, // for Div nodes inside a loop (control input inside loop) without a use except for an UCT (outside the loop). // Rewire control of n to right outside of the loop, regardless if its input(s) are later sunk or not. - _igvn.replace_input_of(n, 0, place_outside_loop(n_ctrl, loop_ctrl)); + Node* maybe_pinned_n = n; + Node* outside_ctrl = place_outside_loop(n_ctrl, loop_ctrl); + if (n->depends_only_on_test()) { + Node* pinned_clone = n->pin_array_access_node(); + if (pinned_clone != nullptr) { + // Pin array access nodes: if this is an array load, it's going to be dependent on a condition that's not a + // range check for that access. If that condition is replaced by an identical dominating one, then an + // unpinned load would risk floating above its range check. + register_new_node(pinned_clone, n_ctrl); + maybe_pinned_n = pinned_clone; + _igvn.replace_node(n, pinned_clone); + } + } + _igvn.replace_input_of(maybe_pinned_n, 0, outside_ctrl); } } if (n_loop != _ltree_root && n->outcnt() > 1) { @@ -1678,7 +1700,16 @@ void PhaseIdealLoop::try_sink_out_of_loop(Node* n) { for (DUIterator_Last jmin, j = n->last_outs(jmin); j >= jmin;) { Node* u = n->last_out(j); // Clone private computation per use _igvn.rehash_node_delayed(u); - Node* x = n->clone(); // Clone computation + Node* x = nullptr; + if (n->depends_only_on_test()) { + // Pin array access nodes: if this is an array load, it's going to be dependent on a condition that's not a + // range check for that access. If that condition is replaced by an identical dominating one, then an + // unpinned load would risk floating above its range check. + x = n->pin_array_access_node(); + } + if (x == nullptr) { + x = n->clone(); + } Node* x_ctrl = nullptr; if (u->is_Phi()) { // Replace all uses of normal nodes. Replace Phi uses @@ -2228,6 +2259,20 @@ void PhaseIdealLoop::clone_loop_handle_data_uses(Node* old, Node_List &old_new, // We notify all uses of old, including use, and the indirect uses, // that may now be optimized because we have replaced old with phi. _igvn.add_users_to_worklist(old); + if (idx == 0 && + use->depends_only_on_test()) { + Node* pinned_clone = use->pin_array_access_node(); + if (pinned_clone != nullptr) { + // Pin array access nodes: control is updated here to a region. If, after some transformations, only one path + // into the region is left, an array load could become dependent on a condition that's not a range check for + // that access. If that condition is replaced by an identical dominating one, then an unpinned load would risk + // floating above its range check. + pinned_clone->set_req(0, phi); + register_new_node(pinned_clone, get_ctrl(use)); + _igvn.replace_node(use, pinned_clone); + continue; + } + } _igvn.replace_input_of(use, idx, phi); if( use->_idx >= new_counter ) { // If updating new phis // Not needed for correctness, but prevents a weak assert @@ -3863,6 +3908,19 @@ bool PhaseIdealLoop::partial_peel( IdealLoopTree *loop, Node_List &old_new ) { if (!n->is_CFG() && n->in(0) != nullptr && not_peel.test(n->_idx) && peel.test(n->in(0)->_idx)) { Node* n_clone = old_new[n->_idx]; + if (n_clone->depends_only_on_test()) { + // Pin array access nodes: control is updated here to the loop head. If, after some transformations, the + // backedge is removed, an array load could become dependent on a condition that's not a range check for that + // access. If that condition is replaced by an identical dominating one, then an unpinned load would risk + // floating above its range check. + Node* pinned_clone = n_clone->pin_array_access_node(); + if (pinned_clone != nullptr) { + register_new_node(pinned_clone, get_ctrl(n_clone)); + old_new.map(n->_idx, pinned_clone); + _igvn.replace_node(n_clone, pinned_clone); + n_clone = pinned_clone; + } + } _igvn.replace_input_of(n_clone, 0, new_head_clone); } } diff --git a/src/hotspot/share/opto/split_if.cpp b/src/hotspot/share/opto/split_if.cpp index 3356498ce13f7..dafd5ffdb38ae 100644 --- a/src/hotspot/share/opto/split_if.cpp +++ b/src/hotspot/share/opto/split_if.cpp @@ -727,6 +727,14 @@ void PhaseIdealLoop::do_split_if(Node* iff, RegionNode** new_false_region, Regio } // End of while merge point has phis _igvn.remove_dead_node(region); + if (iff->Opcode() == Op_RangeCheck) { + // Pin array access nodes: control is updated here to a region. If, after some transformations, only one path + // into the region is left, an array load could become dependent on a condition that's not a range check for + // that access. If that condition is replaced by an identical dominating one, then an unpinned load would risk + // floating above its range check. + pin_array_access_nodes_dependent_on(new_true); + pin_array_access_nodes_dependent_on(new_false); + } if (new_false_region != nullptr) { *new_false_region = new_false; @@ -737,3 +745,18 @@ void PhaseIdealLoop::do_split_if(Node* iff, RegionNode** new_false_region, Regio DEBUG_ONLY( if (VerifyLoopOptimizations) { verify(); } ); } + +void PhaseIdealLoop::pin_array_access_nodes_dependent_on(Node* ctrl) { + for (DUIterator i = ctrl->outs(); ctrl->has_out(i); i++) { + Node* use = ctrl->out(i); + if (!use->depends_only_on_test()) { + continue; + } + Node* pinned_clone = use->pin_array_access_node(); + if (pinned_clone != nullptr) { + register_new_node(pinned_clone, get_ctrl(use)); + _igvn.replace_node(use, pinned_clone); + --i; + } + } +} diff --git a/test/hotspot/jtreg/compiler/rangechecks/TestArrayAccessAboveRCAfterPartialPeeling.java b/test/hotspot/jtreg/compiler/rangechecks/TestArrayAccessAboveRCAfterPartialPeeling.java new file mode 100644 index 0000000000000..c5b99593c62f8 --- /dev/null +++ b/test/hotspot/jtreg/compiler/rangechecks/TestArrayAccessAboveRCAfterPartialPeeling.java @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2024, Red Hat, Inc. All rights reserved. + * 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 8323274 + * @summary partial peeling loop can cause an array load to become dependent on a test other than its range check + * @run main/othervm -XX:-UseOnStackReplacement -XX:-TieredCompilation -XX:-BackgroundCompilation TestArrayAccessAboveRCAfterPartialPeeling + */ + +public class TestArrayAccessAboveRCAfterPartialPeeling { + private static volatile int volatileField; + + public static void main(String[] args) { + int[] array = new int[100]; + for (int i = 0; i < 20_000; i++) { + test(array, 2, true, 1); + test(array, 2, false, 1); + inlined(array, 2, 42, true, 42, 1, 1); + inlined(array, 2, 42, false, 42, 1, 1); + } + try { + test(array, 2, true, -1); + } catch (ArrayIndexOutOfBoundsException arrayIndexOutOfBoundsException) { + } + } + + private static int test(int[] array, int k, boolean flag, int j) { + int l; + for (l = 1; l < 2; l *= 2) { + + } + int m; + for (m = 0; m < 42; m += l) { + + } + int n; + for (n = 0; n < 10; n += m/42) { + + } + return inlined(array, k, l, flag, m, n/10, j); + } + + private static int inlined(int[] array, int k, int l, boolean flag, int m, int n, int j) { + if (array == null) { + } + int[] otherArray = new int[100]; + int i = 0; + int v = 0; + if (k == m) { + } + + if (flag) { + v += array[j]; + v += otherArray[i]; + + for (; ; ) { + synchronized (new Object()) { + } + if (j >= 100) { + break; + } + if (k == 42) { + } + v += array[j]; + v += otherArray[i]; + if (i >= n) { + otherArray[i] = v; + } + v += array[j]; + if (l == 2) { + break; + } + i++; + j *= 2; + volatileField = 42; + k = 2; + l = 42; + } + } else { + v += array[j]; + v += otherArray[i]; + + for (; ; ) { + synchronized (new Object()) { + } + if (j >= 100) { + break; + } + if (k == 42) { + } + v += array[j]; + v += otherArray[i]; + if (i >= n) { + otherArray[i] = v; + } + v += array[j]; + if (l == 2) { + break; + } + i++; + j *= 2; + volatileField = 42; + k = 2; + l = 42; + } + } + return v; + } +} diff --git a/test/hotspot/jtreg/compiler/rangechecks/TestArrayAccessAboveRCAfterSinking.java b/test/hotspot/jtreg/compiler/rangechecks/TestArrayAccessAboveRCAfterSinking.java new file mode 100644 index 0000000000000..a06fb40db29d4 --- /dev/null +++ b/test/hotspot/jtreg/compiler/rangechecks/TestArrayAccessAboveRCAfterSinking.java @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2024, Red Hat, Inc. All rights reserved. + * 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 8323274 + * @summary sinking an array load out of loop can cause it to become dependent on a test other than its range check + * @run main/othervm -XX:-UseOnStackReplacement -XX:-TieredCompilation -XX:-BackgroundCompilation TestArrayAccessAboveRCAfterSinking + */ + + +import java.util.Arrays; + +public class TestArrayAccessAboveRCAfterSinking { + public static void main(String[] args) { + boolean[] allFalse = new boolean[100]; + boolean[] allTrue = new boolean[100]; + Arrays.fill(allTrue, true); + int[] array = new int[100]; + for (int i = 0; i < 20_000; i++) { + test1(allTrue, array, 0, true, 0); + test1(allTrue, array, 0, false, 0); + inlined1(allFalse, array, 2, 0); + inlined1(allFalse, array, 42, 0); + inlined1(allTrue, array, 2, 0); + test2(allTrue, array, 0, true, 0); + test2(allTrue, array, 0, false, 0); + inlined2(allFalse, array, 2, 0); + inlined2(allFalse, array, 42, 0); + inlined2(allTrue, array, 2, 0); + } + try { + test1(allTrue, array, -1, true, 0); + } catch (ArrayIndexOutOfBoundsException arrayIndexOutOfBoundsException) { + } + try { + test2(allTrue, array, -1, true, 0); + } catch (ArrayIndexOutOfBoundsException arrayIndexOutOfBoundsException) { + } + } + + private static int test1(boolean[] flags, int[] array, int k, boolean flag, int v) { + if (flags == null) { + } + if (array == null) { + } + int j = 1; + for (; j < 2; j *= 2) { + } + int i; + for (i = 0; i < 10; i += j) { + + } + if (flags[i - 10]) { + if (flag) { + return inlined1(flags, array, j, k); + } else { + return inlined1(flags, array, j, k) + v; + } + } + return 0; + } + + private static int inlined1(boolean[] flags, int[] array, int j, int k) { + for (int i = 0; i < 100; i++) { + final boolean flag = flags[i & (j - 3)]; + int v = array[i + k]; + if (flag) { + return v; + } + if (j + (i & (j - 2)) == 2) { + break; + } + } + return 0; + } + + private static int test2(boolean[] flags, int[] array, int k, boolean flag, int v) { + if (flags == null) { + } + if (array == null) { + } + int j = 1; + for (; j < 2; j *= 2) { + } + int i; + for (i = 0; i < 10; i += j) { + + } + if (flags[i - 10]) { + if (flag) { + return inlined2(flags, array, j, k); + } else { + return inlined2(flags, array, j, k) + v; + } + } + return 0; + } + + private static int inlined2(boolean[] flags, int[] array, int j, int k) { + for (int i = 0; i < 100; i++) { + int v = array[i + k]; + if (flags[i & (j - 3)]) { + return v; + } + if (j + (i & (j - 2)) == 2) { + break; + } + } + return 0; + } +} diff --git a/test/hotspot/jtreg/compiler/rangechecks/TestArrayAccessAboveRCAfterSplitIf.java b/test/hotspot/jtreg/compiler/rangechecks/TestArrayAccessAboveRCAfterSplitIf.java new file mode 100644 index 0000000000000..e1e3969cfcdb5 --- /dev/null +++ b/test/hotspot/jtreg/compiler/rangechecks/TestArrayAccessAboveRCAfterSplitIf.java @@ -0,0 +1,161 @@ +/* + * Copyright (c) 2024, Red Hat, Inc. All rights reserved. + * 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 8323274 + * @summary split if can cause an array load to become dependent on a test other than its range check + * @run main/othervm -XX:-TieredCompilation -XX:-BackgroundCompilation TestArrayAccessAboveRCAfterSplitIf + */ + +public class TestArrayAccessAboveRCAfterSplitIf { + private static volatile int volatileField; + + public static void main(String[] args) { + int[] array = new int[1000]; + for (int i = 0; i < 20_000; i++) { + test1(array, array, 0, 2, true); + inlined1(42, array, array, 0, 2, 10, true); + inlined1(2, array, array, 0, 2, 10, true); + inlined1(42, array, array, 0, 2, 10, false); + inlined1(2, array, array, 0, 2, 10, false); + test2(array, array, 0, 2, true); + inlined2(42, array, array, 0, 2, 10, true); + inlined2(2, array, array, 0, 2, 10, true); + inlined2(42, array, array, 0, 2, 10, false); + inlined2(2, array, array, 0, 2, 10, false); + } + try { + test1(array, array, -1, 2, true); + } catch (ArrayIndexOutOfBoundsException arrayIndexOutOfBoundsException) { + } + try { + test2(array, array, -1, 2, true); + } catch (ArrayIndexOutOfBoundsException arrayIndexOutOfBoundsException) { + } + } + + private static int test1(int[] array1, int[] array2, int i, int l, boolean flag) { + for (int j = 0; j < 10; j++) { + } + int k; + for (k = 1; k < 2; k *= 2) { + + } + int m; + for (m = 0; m < 10; m+=k) { + + } + return inlined1(k, array1, array2, i, l, m, flag); + } + + private static int inlined1(int k, int[] array1, int[] array2, int i, int l, int m, boolean flag) { + int v; + int[] array; + if (array1 == null) { + } + if (l == 10) { + + } + if (flag) { + if (k == 2) { + v = array1[i]; + array = array1; + if (l == m) { + } + } else { + v = array2[i]; + array = array2; + } + v += array[i]; + v += array2[i]; + } else { + if (k == 2) { + v = array1[i]; + array = array1; + if (l == m) { + } + } else { + v = array2[i]; + array = array2; + } + v += array[i]; + v += array2[i]; + } + return v; + } + + private static int test2(int[] array1, int[] array2, int i, int l, boolean flag) { + for (int j = 0; j < 10; j++) { + } + int k; + for (k = 1; k < 2; k *= 2) { + + } + int m; + for (m = 0; m < 10; m+=k) { + + } + return inlined2(k, array1, array2, i, l, m, flag); + } + + private static int inlined2(int k, int[] array1, int[] array2, int i, int l, int m, boolean flag) { + int v; + int[] array; + if (array1 == null) { + } + if (l == 10) { + + } + if (flag) { + if (k == 2) { + v = array1[i]; + array = array1; + if (l == m) { + } + } else { + v = array2[i]; + array = array2; + } + if (Integer.compareUnsigned(i, array.length) >= 0) { + } + v += array[i]; + v += array2[i]; + } else { + if (k == 2) { + v = array1[i]; + array = array1; + if (l == m) { + } + } else { + v = array2[i]; + array = array2; + } + if (Integer.compareUnsigned(i, array.length) >= 0) { + } + v += array[i]; + v += array2[i]; + } + return v; + } +} diff --git a/test/hotspot/jtreg/compiler/rangechecks/TestArrayAccessAboveRCAfterUnswitching.java b/test/hotspot/jtreg/compiler/rangechecks/TestArrayAccessAboveRCAfterUnswitching.java new file mode 100644 index 0000000000000..1fc7111ff8275 --- /dev/null +++ b/test/hotspot/jtreg/compiler/rangechecks/TestArrayAccessAboveRCAfterUnswitching.java @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2024, Red Hat, Inc. All rights reserved. + * 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 8323274 + * @summary loop unswitching can cause an array load to become dependent on a test other than its range check + * @run main/othervm -XX:-TieredCompilation -XX:-BackgroundCompilation -XX:CompileOnly=TestArrayAccessAboveRCAfterUnswitching::test + * -XX:+UnlockDiagnosticVMOptions -XX:+StressGCM -XX:StressSeed=148059521 TestArrayAccessAboveRCAfterUnswitching + * @run main/othervm -XX:-TieredCompilation -XX:-BackgroundCompilation -XX:CompileOnly=TestArrayAccessAboveRCAfterUnswitching::test + * -XX:+UnlockDiagnosticVMOptions -XX:+StressGCM TestArrayAccessAboveRCAfterUnswitching + */ + +import java.util.Arrays; + +public class TestArrayAccessAboveRCAfterUnswitching { + private static int field; + + public static void main(String[] args) { + int[] array = new int[1000]; + boolean[] allFalse = new boolean[1000]; + boolean[] allTrue = new boolean[1000]; + Arrays.fill(allTrue, true); + for (int i = 0; i < 20_000; i++) { + inlined(array, allFalse, 42, 2, 2, 0); + inlined(array, allFalse, 2, 42, 2, 0); + inlined(array, allFalse, 2, 2, 2, 0); + inlined(array, allFalse, 2, 2, 42, 0); + inlined(array, allTrue, 2, 2, 2, 0); + test(array, allTrue, 0); + } + try { + test(array, allTrue, -1); + } catch (ArrayIndexOutOfBoundsException aioobe) { + } + } + + private static int test(int[] array, boolean[] flags, int start) { + if (flags == null) { + } + if (array == null) { + } + int j = 1; + for (; j < 2; j *= 2) { + } + int k = 1; + for (; k < 2; k *= 2) { + } + int l = 1; + for (; l < 2; l *= 2) { + } + int i; + for (i = 0; i < 10; i += l) { + + } + if (flags[i - 10]) { + return inlined(array, flags, j, k, l, start); + } + return 0; + } + + private static int inlined(int[] array, boolean[] flags, int j, int k, int l, int start) { + for (int i = 0; i < 100; i++) { + final boolean flag = flags[i & (j - 3)]; + int v = array[(i + start) & (j - 3)]; + if (flag) { + return v; + } + if (j != 2) { + field = v; + } else { + if (k != 2) { + field = 42; + } else { + if (l == 2) { + break; + } + } + } + } + return 0; + } +} diff --git a/test/hotspot/jtreg/compiler/rangechecks/TestArrayAccessAboveRCForArrayCopyLoad.java b/test/hotspot/jtreg/compiler/rangechecks/TestArrayAccessAboveRCForArrayCopyLoad.java new file mode 100644 index 0000000000000..46438579f4a44 --- /dev/null +++ b/test/hotspot/jtreg/compiler/rangechecks/TestArrayAccessAboveRCForArrayCopyLoad.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2024, Red Hat, Inc. All rights reserved. + * 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 8323274 + * @summary converting an array copy to a series of loads/stores add loads that can float + * @run main/othervm -XX:-UseOnStackReplacement -XX:-TieredCompilation -XX:-BackgroundCompilation TestArrayAccessAboveRCForArrayCopyLoad + */ + +public class TestArrayAccessAboveRCForArrayCopyLoad { + public static void main(String[] args) { + int[] array = new int[10]; + for (int i = 0; i < 20_000; i++) { + test(array, 0, array, 1, false); + test(array, 0, array, 1, true); + } + try { + test(array, -1, array, 0, true); + } catch (ArrayIndexOutOfBoundsException arrayIndexOutOfBoundsException) { + + } + } + + private static void test(int[] src, int srcPos, int[] dst, int dstPos, boolean flag) { + if (src == null) { + } + if (srcPos < dstPos) { + if (flag) { + System.arraycopy(src, srcPos, dst, dstPos, 2); + } else { + System.arraycopy(src, srcPos, dst, dstPos, 2); + } + } + } +} From f365d807e5552a6ad9a36afd82db8f0881d62cc3 Mon Sep 17 00:00:00 2001 From: Fredrik Bredberg Date: Thu, 22 Feb 2024 13:07:32 +0000 Subject: [PATCH 18/38] 8325153: SEGV in stackChunkOopDesc::derelativize_address(int) Reviewed-by: stefank, coleenp --- src/hotspot/share/prims/stackwalk.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/hotspot/share/prims/stackwalk.cpp b/src/hotspot/share/prims/stackwalk.cpp index 04a50ff611f27..20f7ecefe1145 100644 --- a/src/hotspot/share/prims/stackwalk.cpp +++ b/src/hotspot/share/prims/stackwalk.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -544,7 +544,11 @@ jint StackWalk::fetchNextBatch(Handle stackStream, jint mode, jlong magic, // the continuation and it returns to let Java side set the continuation. // Now this batch starts right at the first frame of another continuation. if (last_batch_count > 0) { - log_debug(stackwalk)("advanced past %s", stream.method()->external_name()); + // It is not always safe to dig out the name of the last frame + // here, i.e. stream.method()->external_name(), since it may + // have been reclaimed by HandleMark::pop_and_restore() together + // with the rest of the previous batch. + log_debug(stackwalk)("advanced past last frame decoded in the previous batch"); stream.next(); } From 724a2a2c4a6020188b7907509cd48aa126b79b0f Mon Sep 17 00:00:00 2001 From: Renjith Kannath Pariyangad Date: Thu, 22 Feb 2024 14:05:02 +0000 Subject: [PATCH 19/38] 8321192: j.a.PrintJob/ImageTest/ImageTest.java: Fail or skip the test if there's no printer Reviewed-by: aivanov, tr --- .../awt/PrintJob/ImageTest/ImageTest.java | 164 ++++++++++++++++++ 1 file changed, 164 insertions(+) create mode 100644 test/jdk/java/awt/PrintJob/ImageTest/ImageTest.java diff --git a/test/jdk/java/awt/PrintJob/ImageTest/ImageTest.java b/test/jdk/java/awt/PrintJob/ImageTest/ImageTest.java new file mode 100644 index 0000000000000..603623819de6b --- /dev/null +++ b/test/jdk/java/awt/PrintJob/ImageTest/ImageTest.java @@ -0,0 +1,164 @@ +/* + * Copyright (c) 1999, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import java.awt.Button; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.FlowLayout; +import java.awt.Frame; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.Image; +import java.awt.PrintJob; +import java.awt.Toolkit; +import java.awt.image.BufferedImage; +import java.awt.print.PrinterJob; +import java.io.File; +import java.io.IOException; +import javax.imageio.ImageIO; + +/* + * @test + * @bug 4242308 4255603 + * @key printer + * @library /java/awt/regtesthelpers + * @build PassFailJFrame + * @summary Tests printing of images + * @run main/manual ImageTest + */ +public final class ImageTest { + + private static final class ImageFrame extends Frame { + final Image img; + PrintJob pjob; + + private ImageFrame() { + super("ImageFrame"); + img = getToolkit().getImage("image.gif"); + } + + @Override + public void paint(Graphics g) { + int width = img.getWidth(this); + int height = img.getHeight(this); + if (pjob != null) { + System.out.println("Size " + pjob.getPageDimension()); + Dimension dim = pjob.getPageDimension(); + if (width > dim.width) { + width = dim.width - 30; // take care of paper margin + } + if (height > dim.height) { + height = dim.height - 30; + } + } + g.drawImage(img, 10, 75, width, height, this); + } + + private void setPrintJob(PrintJob pj) { + pjob = pj; + } + + @Override + public boolean imageUpdate(Image img, int infoflags, + int x, int y, int w, int h) { + if ((infoflags & ALLBITS) != 0) { + repaint(); + return false; + } + return true; + } + } + + private static Frame init() { + ImageFrame f = new ImageFrame(); + f.setLayout(new FlowLayout()); + Button b = new Button("Print"); + b.addActionListener(e -> { + PrintJob pj = Toolkit.getDefaultToolkit() + .getPrintJob(f, "ImageTest", null); + if (pj != null) { + f.setPrintJob(pj); + Graphics pg = pj.getGraphics(); + f.paint(pg); + pg.dispose(); + pj.end(); + } + }); + f.add(b); + f.setSize(700, 350); + + return f; + } + + private static void createImage() throws IOException { + final BufferedImage bufferedImage = + new BufferedImage(600, 230, BufferedImage.TYPE_INT_RGB); + Graphics2D g2d = bufferedImage.createGraphics(); + + g2d.setColor(new Color(0xE7E7E7)); + g2d.fillRect(0, 0, bufferedImage.getWidth(), bufferedImage.getHeight()); + + g2d.setColor(Color.YELLOW); + g2d.fillRect(0, 6, 336, 40); + g2d.setColor(Color.BLACK); + g2d.drawString("Yellow rectangle", 10, 30); + + g2d.setColor(Color.CYAN); + g2d.fillRect(132, 85, 141, 138); + g2d.setColor(Color.BLACK); + g2d.drawString("Cyan rectangle", 142, 148); + + g2d.setColor(Color.MAGENTA); + g2d.fillRect(432, 85, 141, 138); + g2d.setColor(Color.BLACK); + g2d.drawString("Magenta rectangle", 442, 148); + + g2d.dispose(); + + ImageIO.write(bufferedImage, "gif", new File("image.gif")); + } + + private static final String INSTRUCTIONS = """ + Click the Print button on the Frame. Select a printer from the + print dialog and click 'OK'. Verify that the image displayed + in the Frame is correctly printed. Test printing in both Color + and Monochrome. + """; + + public static void main(String[] args) throws Exception { + + if (PrinterJob.lookupPrintServices().length == 0) { + throw new RuntimeException("Printer not configured or available."); + } + + createImage(); + + PassFailJFrame.builder() + .instructions(INSTRUCTIONS) + .rows((int) INSTRUCTIONS.lines().count() + 1) + .columns(45) + .testUI(ImageTest::init) + .build() + .awaitAndCheck(); + } +} From 8e5c0ee402be597f6354ea870d3d5d1f43051e65 Mon Sep 17 00:00:00 2001 From: Erik Gahlin Date: Thu, 22 Feb 2024 15:29:05 +0000 Subject: [PATCH 20/38] 8324832: JFR: Improve sorting of 'jfr summary' Reviewed-by: mgronlun --- src/jdk.jfr/share/classes/jdk/jfr/internal/tool/Summary.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/tool/Summary.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/tool/Summary.java index a092cf69094df..12a468b2e89e4 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/tool/Summary.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/tool/Summary.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -144,6 +144,7 @@ private void printInformation(Path p) throws IOException { println(" Start: " + DATE_FORMAT.format(Instant.ofEpochSecond(epochSeconds, adjustNanos)) + " (UTC)"); println(" Duration: " + (totalDuration + 500_000_000) / 1_000_000_000 + " s"); List statsList = new ArrayList<>(stats.values()); + statsList.sort((u, v) -> u.name.compareTo(v.name)); statsList.sort((u, v) -> Long.compare(v.count, u.count)); println(); String header = " Count Size (bytes) "; From 864cf22241281721a0f0ddbe96cd43b4e3c5520c Mon Sep 17 00:00:00 2001 From: Stefan Karlsson Date: Thu, 22 Feb 2024 15:58:41 +0000 Subject: [PATCH 21/38] 8325742: Remove MetaWord usage from MemRegion Reviewed-by: coleenp, tschatzl --- src/hotspot/share/memory/memRegion.hpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/hotspot/share/memory/memRegion.hpp b/src/hotspot/share/memory/memRegion.hpp index 3d5cf5f06e3ee..5d3d635c650cd 100644 --- a/src/hotspot/share/memory/memRegion.hpp +++ b/src/hotspot/share/memory/memRegion.hpp @@ -54,10 +54,6 @@ class MemRegion { _start(start), _word_size(pointer_delta(end, start)) { assert(end >= start, "incorrect constructor arguments"); } - MemRegion(MetaWord* start, MetaWord* end) : - _start((HeapWord*)start), _word_size(pointer_delta(end, start)) { - assert(end >= start, "incorrect constructor arguments"); - } MemRegion intersection(const MemRegion mr2) const; // regions must overlap or be adjacent From 9f9a732c38072b8168b3c0caee9069f34f6655a8 Mon Sep 17 00:00:00 2001 From: Stefan Karlsson Date: Thu, 22 Feb 2024 16:02:26 +0000 Subject: [PATCH 22/38] 8325752: Remove badMetaWordVal Reviewed-by: coleenp, tschatzl --- src/hotspot/share/oops/oop.cpp | 4 ---- src/hotspot/share/oops/stackChunkOop.cpp | 2 -- src/hotspot/share/utilities/globalDefinitions.hpp | 1 - 3 files changed, 7 deletions(-) diff --git a/src/hotspot/share/oops/oop.cpp b/src/hotspot/share/oops/oop.cpp index 17a9bf9c41296..65727374df872 100644 --- a/src/hotspot/share/oops/oop.cpp +++ b/src/hotspot/share/oops/oop.cpp @@ -43,8 +43,6 @@ void oopDesc::print_on(outputStream* st) const { if (*((juint*)this) == badHeapWordVal) { st->print_cr("BAD WORD"); - } else if (*((juint*)this) == badMetaWordVal) { - st->print_cr("BAD META WORD"); } else { klass()->oop_print_on(cast_to_oop(this), st); } @@ -58,8 +56,6 @@ void oopDesc::print_address_on(outputStream* st) const { void oopDesc::print_name_on(outputStream* st) const { if (*((juint*)this) == badHeapWordVal) { st->print_cr("BAD WORD"); - } else if (*((juint*)this) == badMetaWordVal) { - st->print_cr("BAD META WORD"); } else { st->print_cr("%s", klass()->external_name()); } diff --git a/src/hotspot/share/oops/stackChunkOop.cpp b/src/hotspot/share/oops/stackChunkOop.cpp index 4e771939dc941..e114161625b66 100644 --- a/src/hotspot/share/oops/stackChunkOop.cpp +++ b/src/hotspot/share/oops/stackChunkOop.cpp @@ -411,8 +411,6 @@ template void stackChunkOopDesc::fix_thawed_frame(const frame& f, const SmallReg void stackChunkOopDesc::print_on(bool verbose, outputStream* st) const { if (*((juint*)this) == badHeapWordVal) { st->print_cr("BAD WORD"); - } else if (*((juint*)this) == badMetaWordVal) { - st->print_cr("BAD META WORD"); } else { InstanceStackChunkKlass::print_chunk(const_cast(this), verbose, st); } diff --git a/src/hotspot/share/utilities/globalDefinitions.hpp b/src/hotspot/share/utilities/globalDefinitions.hpp index ce94629c84bd4..b61a51b404d2e 100644 --- a/src/hotspot/share/utilities/globalDefinitions.hpp +++ b/src/hotspot/share/utilities/globalDefinitions.hpp @@ -1040,7 +1040,6 @@ const int uninitBlockPad = 0xF1; // value used to zap const juint uninitMetaWordVal = 0xf7f7f7f7; // value used to zap newly allocated metachunk const jubyte heapPaddingByteVal = 0xBD; // value used to zap object padding in the heap const juint badHeapWordVal = 0xBAADBABE; // value used to zap heap after GC -const juint badMetaWordVal = 0xBAADFADE; // value used to zap metadata heap after GC const int badCodeHeapNewVal = 0xCC; // value used to zap Code heap at allocation const int badCodeHeapFreeVal = 0xDD; // value used to zap Code heap at deallocation const intptr_t badDispHeaderDeopt = 0xDE0BD000; // value to fill unused displaced header during deoptimization From d695af89f6463591e870f631dc816c7729e33567 Mon Sep 17 00:00:00 2001 From: Thomas Stuefe Date: Thu, 22 Feb 2024 17:45:34 +0000 Subject: [PATCH 23/38] 8326376: java -version failed with CONF=fastdebug -XX:InitialCodeCacheSize=1024K -XX:ReservedCodeCacheSize=1200k Reviewed-by: kvn, jwaters --- src/hotspot/share/compiler/compilationFailureInfo.cpp | 11 ++++++++--- src/hotspot/share/compiler/compilationFailureInfo.hpp | 5 +++-- .../hotspot/jtreg/compiler/startup/StartupOutput.java | 11 ++++++++++- 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/src/hotspot/share/compiler/compilationFailureInfo.cpp b/src/hotspot/share/compiler/compilationFailureInfo.cpp index e3f3353589e54..fb94102ef1654 100644 --- a/src/hotspot/share/compiler/compilationFailureInfo.cpp +++ b/src/hotspot/share/compiler/compilationFailureInfo.cpp @@ -1,6 +1,6 @@ /* - * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. - * Copyright (c) 2023, Red Hat, Inc. and/or its affiliates. + * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2024, Red Hat, Inc. and/or its affiliates. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -42,11 +42,16 @@ #include "utilities/ostream.hpp" #include "utilities/nativeCallStack.hpp" +int CompilationFailureInfo::current_compile_id_or_0() { + ciEnv* env = ciEnv::current(); + return (env != nullptr) ? env->compile_id() : 0; +} + CompilationFailureInfo::CompilationFailureInfo(const char* failure_reason) : _stack(2), _failure_reason(os::strdup(failure_reason)), _elapsed_seconds(os::elapsedTime()), - _compile_id(ciEnv::current()->task()->compile_id()) + _compile_id(current_compile_id_or_0()) {} CompilationFailureInfo::~CompilationFailureInfo() { diff --git a/src/hotspot/share/compiler/compilationFailureInfo.hpp b/src/hotspot/share/compiler/compilationFailureInfo.hpp index 3de62eb69da5a..470865a2f6608 100644 --- a/src/hotspot/share/compiler/compilationFailureInfo.hpp +++ b/src/hotspot/share/compiler/compilationFailureInfo.hpp @@ -1,6 +1,6 @@ /* - * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. - * Copyright (c) 2023, Red Hat, Inc. and/or its affiliates. + * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2024, Red Hat, Inc. and/or its affiliates. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -40,6 +40,7 @@ class CompilationFailureInfo : public CHeapObj { char* const _failure_reason; const double _elapsed_seconds; const int _compile_id; + static int current_compile_id_or_0(); public: CompilationFailureInfo(const char* failure_reason); ~CompilationFailureInfo(); diff --git a/test/hotspot/jtreg/compiler/startup/StartupOutput.java b/test/hotspot/jtreg/compiler/startup/StartupOutput.java index d97bcd0019a53..f74a03b226f85 100644 --- a/test/hotspot/jtreg/compiler/startup/StartupOutput.java +++ b/test/hotspot/jtreg/compiler/startup/StartupOutput.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -55,5 +55,14 @@ public static void main(String[] args) throws Exception { if (exitCode != 1 && exitCode != 0) { throw new Exception("VM crashed with exit code " + exitCode); } + + pb = ProcessTools.createLimitedTestJavaProcessBuilder("-XX:InitialCodeCacheSize=1024K", "-XX:ReservedCodeCacheSize=1200k", "-version"); + out = new OutputAnalyzer(pb.start()); + // The VM should not crash but will probably fail with a "CodeCache is full. Compiler has been disabled." message + out.stdoutShouldNotContain("# A fatal error"); + exitCode = out.getExitValue(); + if (exitCode != 1 && exitCode != 0) { + throw new Exception("VM crashed with exit code " + exitCode); + } } } From 00ffc42cef79d82b2f417c133a48bffec4c7e6b9 Mon Sep 17 00:00:00 2001 From: Justin Lu Date: Thu, 22 Feb 2024 22:27:12 +0000 Subject: [PATCH 24/38] 8318761: MessageFormat pattern support for CompactNumberFormat, ListFormat, and DateTimeFormatter Reviewed-by: naoto, rriggs --- .../share/classes/java/text/DateFormat.java | 25 + .../classes/java/text/MessageFormat.java | 929 ++++++++++++------ .../share/classes/java/text/NumberFormat.java | 29 +- .../MessageFormat/CompactSubFormats.java | 91 ++ .../Format/MessageFormat/ListSubFormats.java | 98 ++ .../MessageFormatExceptions.java | 14 +- .../MessageFormat/TemporalSubFormats.java | 204 ++++ 7 files changed, 1061 insertions(+), 329 deletions(-) create mode 100644 test/jdk/java/text/Format/MessageFormat/CompactSubFormats.java create mode 100644 test/jdk/java/text/Format/MessageFormat/ListSubFormats.java create mode 100644 test/jdk/java/text/Format/MessageFormat/TemporalSubFormats.java diff --git a/src/java.base/share/classes/java/text/DateFormat.java b/src/java.base/share/classes/java/text/DateFormat.java index 335fe1105a6aa..d7a4b5a39b72a 100644 --- a/src/java.base/share/classes/java/text/DateFormat.java +++ b/src/java.base/share/classes/java/text/DateFormat.java @@ -482,6 +482,31 @@ public Object parseObject(String source, ParsePosition pos) { */ public static final int DEFAULT = MEDIUM; + /** + * A DateFormat style. + * {@code Style} is an enum which corresponds to the DateFormat style + * constants. Use {@code getValue()} to retrieve the associated int style + * value. + */ + enum Style { + + FULL(DateFormat.FULL), + LONG(DateFormat.LONG), + MEDIUM(DateFormat.MEDIUM), + SHORT(DateFormat.SHORT), + DEFAULT(DateFormat.MEDIUM); + + private final int value; + + Style(int value){ + this.value = value; + } + + int getValue() { + return value; + } + } + /** * Gets the time formatter with the default formatting style * for the default {@link java.util.Locale.Category#FORMAT FORMAT} locale. diff --git a/src/java.base/share/classes/java/text/MessageFormat.java b/src/java.base/share/classes/java/text/MessageFormat.java index c309801bf0cc0..8f939f8af2669 100644 --- a/src/java.base/share/classes/java/text/MessageFormat.java +++ b/src/java.base/share/classes/java/text/MessageFormat.java @@ -38,10 +38,10 @@ package java.text; -import java.io.InvalidObjectException; import java.io.IOException; +import java.io.InvalidObjectException; import java.io.ObjectInputStream; -import java.text.DecimalFormat; +import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.Arrays; import java.util.Date; @@ -52,7 +52,7 @@ /** * {@code MessageFormat} provides a means to produce concatenated - * messages in a language-neutral way. Use this to construct messages + * messages in a language-neutral way. Use this class to construct messages * displayed for end users. * *

@@ -82,8 +82,16 @@ * { ArgumentIndex , FormatType } * { ArgumentIndex , FormatType , FormatStyle } * - * FormatType: one of - * number date time choice + * FormatType: + * number + * dtf_date + * dtf_time + * dtf_datetime + * pre-defined DateTimeFormatter(s) + * date + * time + * choice + * list * * FormatStyle: * short @@ -93,48 +101,13 @@ * integer * currency * percent + * compact_short + * compact_long + * or + * unit * SubformatPattern *

* - *

Within a String, a pair of single quotes can be used to - * quote any arbitrary characters except single quotes. For example, - * pattern string "'{0}'" represents string - * "{0}", not a FormatElement. A single quote itself - * must be represented by doubled single quotes {@code ''} throughout a - * String. For example, pattern string "'{''}'" is - * interpreted as a sequence of '{ (start of quoting and a - * left curly brace), {@code ''} (a single quote), and - * }' (a right curly brace and end of quoting), - * not '{' and '}' (quoted left and - * right curly braces): representing string "{'}", - * not "{}". - * - *

A SubformatPattern is interpreted by its corresponding - * subformat, and subformat-dependent pattern rules apply. For example, - * pattern string "{1,number,$'#',##}" - * (SubformatPattern with underline) will produce a number format - * with the pound-sign quoted, with a result such as: {@code - * "$#31,45"}. Refer to each {@code Format} subclass documentation for - * details. - * - *

Any unmatched quote is treated as closed at the end of the given - * pattern. For example, pattern string {@code "'{0}"} is treated as - * pattern {@code "'{0}'"}. - * - *

Any curly braces within an unquoted pattern must be balanced. For - * example, "ab {0} de" and "ab '}' de" are - * valid patterns, but "ab {0'}' de", "ab } de" - * and "''{''" are not. - * - *

Warning:
The rules for using quotes within message - * format patterns unfortunately have shown to be somewhat confusing. - * In particular, it isn't always obvious to localizers whether single - * quotes need to be doubled or not. Make sure to inform localizers about - * the rules, and tell them (for example, by using comments in resource - * bundle source files) which strings will be processed by {@code MessageFormat}. - * Note that localizers may need to use single quotes in translated - * strings where the original version doesn't have them. - *
*

* The ArgumentIndex value is a non-negative integer written * using the digits {@code '0'} through {@code '9'}, and represents an index into the @@ -143,8 +116,9 @@ *

* The FormatType and FormatStyle values are used to create * a {@code Format} instance for the format element. The following - * table shows how the values map to {@code Format} instances. Combinations not - * shown in the table are illegal. A SubformatPattern must + * table shows how the values map to {@code Format} instances. These values + * are case-insensitive when passed to {@link #applyPattern(String)}. Combinations + * not shown in the table are illegal. A SubformatPattern must * be a valid pattern string for the {@code Format} subclass used. * * @@ -161,7 +135,7 @@ * - * @@ -174,109 +148,254 @@ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * * + * * * * * * * * * * * * * * + * + * + * *
(none) * {@code null} *
{@code number} + * {@code number} * (none) * {@link NumberFormat#getInstance(Locale) NumberFormat.getInstance}{@code (getLocale())} *
{@code percent} * {@link NumberFormat#getPercentInstance(Locale) NumberFormat.getPercentInstance}{@code (getLocale())} *
{@code compact_short} + * {@link NumberFormat#getCompactNumberInstance(Locale, NumberFormat.Style) + * NumberFormat.getCompactNumberInstance}{@code (getLocale(),} {@link NumberFormat.Style#SHORT}) + *
{@code compact_long} + * {@link NumberFormat#getCompactNumberInstance(Locale, NumberFormat.Style) + * NumberFormat.getCompactNumberInstance}{@code (getLocale(),} {@link NumberFormat.Style#LONG}) + *
SubformatPattern + * {@code new} {@link DecimalFormat#DecimalFormat(String,DecimalFormatSymbols) + * DecimalFormat}{@code (subformatPattern,} {@link DecimalFormatSymbols#getInstance(Locale) + * DecimalFormatSymbols.getInstance}{@code (getLocale()))} + *
{@code dtf_date} + * (none) + * {@link DateTimeFormatter#ofLocalizedDate(java.time.format.FormatStyle) + * DateTimeFormatter.ofLocalizedDate(}{@link java.time.format.FormatStyle#MEDIUM}{@code ).withLocale(getLocale())} + *
{@code short} + * {@link DateTimeFormatter#ofLocalizedDate(java.time.format.FormatStyle) + * DateTimeFormatter.ofLocalizedDate(}{@link java.time.format.FormatStyle#SHORT}{@code ).withLocale(getLocale())} + *
{@code medium} + * {@link DateTimeFormatter#ofLocalizedDate(java.time.format.FormatStyle) + * DateTimeFormatter.ofLocalizedDate(}{@link java.time.format.FormatStyle#MEDIUM}{@code ).withLocale(getLocale())} + *
{@code long} + * {@link DateTimeFormatter#ofLocalizedDate(java.time.format.FormatStyle) + * DateTimeFormatter.ofLocalizedDate(}{@link java.time.format.FormatStyle#LONG}{@code ).withLocale(getLocale())} + *
{@code full} + * {@link DateTimeFormatter#ofLocalizedDate(java.time.format.FormatStyle) + * DateTimeFormatter.ofLocalizedDate(}{@link java.time.format.FormatStyle#FULL}{@code ).withLocale(getLocale())} + *
SubformatPattern + * {@link DateTimeFormatter#ofPattern(String, Locale) + * DateTimeFormatter.ofPattern}{@code (subformatPattern, getLocale())} + *
{@code dtf_time} + * (none) + * {@link DateTimeFormatter#ofLocalizedTime(java.time.format.FormatStyle) + * DateTimeFormatter.ofLocalizedTime(}{@link java.time.format.FormatStyle#MEDIUM}{@code ).withLocale(getLocale())} + *
{@code short} + * {@link DateTimeFormatter#ofLocalizedTime(java.time.format.FormatStyle) + * DateTimeFormatter.ofLocalizedTime(}{@link java.time.format.FormatStyle#SHORT}{@code ).withLocale(getLocale())} + *
{@code medium} + * {@link DateTimeFormatter#ofLocalizedTime(java.time.format.FormatStyle) + * DateTimeFormatter.ofLocalizedTime(}{@link java.time.format.FormatStyle#MEDIUM}{@code ).withLocale(getLocale())} + *
{@code long} + * {@link DateTimeFormatter#ofLocalizedTime(java.time.format.FormatStyle) + * DateTimeFormatter.ofLocalizedTime(}{@link java.time.format.FormatStyle#LONG}{@code ).withLocale(getLocale())} + *
{@code full} + * {@link DateTimeFormatter#ofLocalizedTime(java.time.format.FormatStyle) + * DateTimeFormatter.ofLocalizedTime(}{@link java.time.format.FormatStyle#FULL}{@code ).withLocale(getLocale())} + *
SubformatPattern + * {@link DateTimeFormatter#ofPattern(String, Locale) DateTimeFormatter.ofPattern}{@code (subformatPattern, getLocale())} + *
{@code dtf_datetime} + * (none) + * {@link DateTimeFormatter#ofLocalizedDateTime(java.time.format.FormatStyle) + * DateTimeFormatter.ofLocalizedDateTime(}{@link java.time.format.FormatStyle#MEDIUM}{@code ).withLocale(getLocale())} + *
{@code short} + * {@link DateTimeFormatter#ofLocalizedDateTime(java.time.format.FormatStyle) + * DateTimeFormatter.ofLocalizedDateTime(}{@link java.time.format.FormatStyle#SHORT}{@code ).withLocale(getLocale())} + *
{@code medium} + * {@link DateTimeFormatter#ofLocalizedDateTime(java.time.format.FormatStyle) + * DateTimeFormatter.ofLocalizedDateTime(}{@link java.time.format.FormatStyle#MEDIUM}{@code ).withLocale(getLocale())} + *
{@code long} + * {@link DateTimeFormatter#ofLocalizedDateTime(java.time.format.FormatStyle) + * DateTimeFormatter.ofLocalizedDateTime(}{@link java.time.format.FormatStyle#LONG}{@code ).withLocale(getLocale())} + *
{@code full} + * {@link DateTimeFormatter#ofLocalizedDateTime(java.time.format.FormatStyle) + * DateTimeFormatter.ofLocalizedDateTime(}{@link java.time.format.FormatStyle#FULL}{@code ).withLocale(getLocale())} + *
SubformatPattern - * {@code new} {@link DecimalFormat#DecimalFormat(String,DecimalFormatSymbols) DecimalFormat}{@code (subformatPattern,} {@link DecimalFormatSymbols#getInstance(Locale) DecimalFormatSymbols.getInstance}{@code (getLocale()))} + * {@link DateTimeFormatter#ofPattern(String, Locale) + * DateTimeFormatter.ofPattern}{@code (subformatPattern, getLocale())} + *
{@code pre-defined DateTimeFormatter(s)} + * (none) + * The {@code pre-defined DateTimeFormatter(s)} are used as a {@code FormatType} : + * {@link DateTimeFormatter#BASIC_ISO_DATE BASIC_ISO_DATE}, + * {@link DateTimeFormatter#ISO_LOCAL_DATE ISO_LOCAL_DATE}, + * {@link DateTimeFormatter#ISO_OFFSET_DATE ISO_OFFSET_DATE}, + * {@link DateTimeFormatter#ISO_DATE ISO_DATE}, + * {@link DateTimeFormatter#ISO_LOCAL_TIME ISO_LOCAL_TIME}, + * {@link DateTimeFormatter#ISO_OFFSET_TIME ISO_OFFSET_TIME}, + * {@link DateTimeFormatter#ISO_TIME ISO_TIME}, + * {@link DateTimeFormatter#ISO_LOCAL_DATE_TIME ISO_LOCAL_DATE_TIME}, + * {@link DateTimeFormatter#ISO_OFFSET_DATE_TIME ISO_OFFSET_DATE_TIME}, + * {@link DateTimeFormatter#ISO_ZONED_DATE_TIME ISO_ZONED_DATE_TIME}, + * {@link DateTimeFormatter#ISO_DATE_TIME ISO_DATE_TIME}, + * {@link DateTimeFormatter#ISO_ORDINAL_DATE ISO_ORDINAL_DATE}, + * {@link DateTimeFormatter#ISO_WEEK_DATE ISO_WEEK_DATE}, + * {@link DateTimeFormatter#ISO_INSTANT ISO_INSTANT}, + * {@link DateTimeFormatter#RFC_1123_DATE_TIME RFC_1123_DATE_TIME} *
{@code date} * (none) - * {@link DateFormat#getDateInstance(int,Locale) DateFormat.getDateInstance}{@code (}{@link DateFormat#DEFAULT}{@code , getLocale())} + * {@link DateFormat#getDateInstance(int,Locale) + * DateFormat.getDateInstance}{@code (}{@link DateFormat#DEFAULT}{@code , getLocale())} *
{@code short} - * {@link DateFormat#getDateInstance(int,Locale) DateFormat.getDateInstance}{@code (}{@link DateFormat#SHORT}{@code , getLocale())} + * {@link DateFormat#getDateInstance(int,Locale) + * DateFormat.getDateInstance}{@code (}{@link DateFormat#SHORT}{@code , getLocale())} *
{@code medium} - * {@link DateFormat#getDateInstance(int,Locale) DateFormat.getDateInstance}{@code (}{@link DateFormat#DEFAULT}{@code , getLocale())} + * {@link DateFormat#getDateInstance(int,Locale) + * DateFormat.getDateInstance}{@code (}{@link DateFormat#MEDIUM}{@code , getLocale())} *
{@code long} - * {@link DateFormat#getDateInstance(int,Locale) DateFormat.getDateInstance}{@code (}{@link DateFormat#LONG}{@code , getLocale())} + * {@link DateFormat#getDateInstance(int,Locale) + * DateFormat.getDateInstance}{@code (}{@link DateFormat#LONG}{@code , getLocale())} *
{@code full} - * {@link DateFormat#getDateInstance(int,Locale) DateFormat.getDateInstance}{@code (}{@link DateFormat#FULL}{@code , getLocale())} + * {@link DateFormat#getDateInstance(int,Locale) + * DateFormat.getDateInstance}{@code (}{@link DateFormat#FULL}{@code , getLocale())} *
SubformatPattern - * {@code new} {@link SimpleDateFormat#SimpleDateFormat(String,Locale) SimpleDateFormat}{@code (subformatPattern, getLocale())} + * {@code new} {@link SimpleDateFormat#SimpleDateFormat(String,Locale) + * SimpleDateFormat}{@code (subformatPattern, getLocale())} *
{@code time} * (none) - * {@link DateFormat#getTimeInstance(int,Locale) DateFormat.getTimeInstance}{@code (}{@link DateFormat#DEFAULT}{@code , getLocale())} + * {@link DateFormat#getTimeInstance(int,Locale) + * DateFormat.getTimeInstance}{@code (}{@link DateFormat#DEFAULT}{@code , getLocale())} *
{@code short} - * {@link DateFormat#getTimeInstance(int,Locale) DateFormat.getTimeInstance}{@code (}{@link DateFormat#SHORT}{@code , getLocale())} + * {@link DateFormat#getTimeInstance(int,Locale) + * DateFormat.getTimeInstance}{@code (}{@link DateFormat#SHORT}{@code , getLocale())} *
{@code medium} - * {@link DateFormat#getTimeInstance(int,Locale) DateFormat.getTimeInstance}{@code (}{@link DateFormat#DEFAULT}{@code , getLocale())} + * {@link DateFormat#getTimeInstance(int,Locale) + * DateFormat.getTimeInstance}{@code (}{@link DateFormat#MEDIUM}{@code , getLocale())} *
{@code long} - * {@link DateFormat#getTimeInstance(int,Locale) DateFormat.getTimeInstance}{@code (}{@link DateFormat#LONG}{@code , getLocale())} + * {@link DateFormat#getTimeInstance(int,Locale) + * DateFormat.getTimeInstance}{@code (}{@link DateFormat#LONG}{@code , getLocale())} *
{@code full} - * {@link DateFormat#getTimeInstance(int,Locale) DateFormat.getTimeInstance}{@code (}{@link DateFormat#FULL}{@code , getLocale())} + * {@link DateFormat#getTimeInstance(int,Locale) + * DateFormat.getTimeInstance}{@code (}{@link DateFormat#FULL}{@code , getLocale())} *
SubformatPattern - * {@code new} {@link SimpleDateFormat#SimpleDateFormat(String,Locale) SimpleDateFormat}{@code (subformatPattern, getLocale())} + * {@code new} {@link SimpleDateFormat#SimpleDateFormat(String,Locale) + * SimpleDateFormat}{@code (subformatPattern, getLocale())} *
{@code choice} * SubformatPattern * {@code new} {@link ChoiceFormat#ChoiceFormat(String) ChoiceFormat}{@code (subformatPattern)} + *
{@code list} + * (none) + * {@link ListFormat#getInstance(Locale, ListFormat.Type, ListFormat.Style) + * ListFormat.getInstance}{@code (getLocale()}, {@link ListFormat.Type#STANDARD}, {@link ListFormat.Style#FULL}) + *
{@code or} + * {@link ListFormat#getInstance(Locale, ListFormat.Type, ListFormat.Style) + * ListFormat.getInstance}{@code (getLocale()}, {@link ListFormat.Type#OR}, {@link ListFormat.Style#FULL}) + *
{@code unit} + * {@link ListFormat#getInstance(Locale, ListFormat.Type, ListFormat.Style) + * ListFormat.getInstance}{@code (getLocale()}, {@link ListFormat.Type#UNIT}, {@link ListFormat.Style#FULL}} *
* - *

Usage Information

+ *

Quoting Rules in Patterns

+ * + *

Within a String, a pair of single quotes can be used to + * quote any arbitrary characters except single quotes. For example, + * pattern string "'{0}'" represents string + * "{0}", not a FormatElement. A single quote itself + * must be represented by doubled single quotes {@code ''} throughout a + * String. For example, pattern string "'{''}'" is + * interpreted as a sequence of '{ (start of quoting and a + * left curly brace), {@code ''} (a single quote), and + * }' (a right curly brace and end of quoting), + * not '{' and '}' (quoted left and + * right curly braces): representing string "{'}", + * not "{}". + * + *

A SubformatPattern is interpreted by its corresponding + * subformat, and subformat-dependent pattern rules apply. For example, + * pattern string "{1,number,$'#',##}" + * (SubformatPattern with underline) will produce a number format + * with the pound-sign quoted, with a result such as: {@code + * "$#31,45"}. Refer to each {@code Format} subclass documentation for + * details. + * + *

Any unmatched quote is treated as closed at the end of the given + * pattern. For example, pattern string {@code "'{0}"} is treated as + * pattern {@code "'{0}'"}. + * + *

Any curly braces within an unquoted pattern must be balanced. For + * example, "ab {0} de" and "ab '}' de" are + * valid patterns, but "ab {0'}' de", "ab } de" + * and "''{''" are not. + * + *

Warning:
The rules for using quotes within message + * format patterns unfortunately have shown to be somewhat confusing. + * In particular, it isn't always obvious to localizers whether single + * quotes need to be doubled or not. Make sure to inform localizers about + * the rules, and tell them (for example, by using comments in resource + * bundle source files) which strings will be processed by {@code MessageFormat}. + * Note that localizers may need to use single quotes in translated + * strings where the original version doesn't have them. + *
+ * + *

Usage Information

* *

- * Here are some examples of usage. - * In real internationalized programs, the message format pattern and other - * static strings will, of course, be obtained from resource bundles. - * Other parameters will be dynamically determined at runtime. + * The following example demonstrates a general usage of {@code MessageFormat}. + * In internationalized programs, the message format pattern and other + * static strings will likely be obtained from resource bundles. + * *

- * The first example uses the static method {@code MessageFormat.format}, - * which internally creates a {@code MessageFormat} for one-time use: * {@snippet lang=java : * int planet = 7; * String event = "a disturbance in the Force"; - * * String result = MessageFormat.format( * "At {1,time} on {1,date}, there was {2} on planet {0,number,integer}.", - * planet, new Date(), event); + * planet, new GregorianCalendar(2053, Calendar.JULY, 3, 12, 30).getTime(), event); * } - * The output is: - *

- * At 12:30 PM on Jul 3, 2053, there was a disturbance in the Force on planet 7.
- * 
- * - *

- * The following example creates a {@code MessageFormat} instance that - * can be used repeatedly: - * {@snippet lang=java : - * int fileCount = 1273; - * String diskName = "MyDisk"; - * Object[] testArgs = {Long.valueOf(fileCount), diskName}; * - * MessageFormat form = new MessageFormat( - * "The disk \"{1}\" contains {0} file(s)."); - * - * System.out.println(form.format(testArgs)); - * } - * The output with different values for {@code fileCount}: + * {@code result} returns the following: *

- * The disk "MyDisk" contains 0 file(s).
- * The disk "MyDisk" contains 1 file(s).
- * The disk "MyDisk" contains 1,273 file(s).
+ * At 12:30:00 PM on Jul 3, 2053, there was a disturbance in the Force on planet 7.
  * 
* *

* For more sophisticated patterns, {@link ChoiceFormat} can be used with * {@code MessageFormat} to produce accurate forms for singular and plural: * {@snippet lang=java : - * MessageFormat msgFmt = new MessageFormat("The disk \"{0}\" contains {1}."); - * double[] fileLimits = {0,1,2}; - * String[] filePart = {"no files","one file","{1,number} files"}; - * ChoiceFormat fileChoices = new ChoiceFormat(fileLimits, filePart); - * msgFmt.setFormatByArgumentIndex(1, fileChoices); - * Object[] args = {"MyDisk", 1273}; - * System.out.println(msgFmt.format(args)); + * MessageFormat msgFmt = new MessageFormat("The disk \"{0}\" contains {1,choice,0#no files|1#one file|1< {1,number,integer} files}."); + * Object[] args = {"MyDisk", fileCount}; + * String result = msgFmt.format(args); * } - * The output with different values for {@code fileCount}: + * + * {@code result} with different values for {@code fileCount}, returns the following: *

  * The disk "MyDisk" contains no files.
  * The disk "MyDisk" contains one file.
@@ -284,15 +403,6 @@
  * 
* *

- * You can create the {@code ChoiceFormat} programmatically, as in the - * above example, or by using a pattern. See {@link ChoiceFormat} - * for more information. - * {@snippet lang=java : - * msgFmt.applyPattern( - * "There {0,choice,0#are no files|1#is one file|1 * Notes: As seen in the previous snippet, * the string produced by a {@code ChoiceFormat} in {@code MessageFormat} is * treated as special; occurrences of '{' are used to indicate subformats, and @@ -304,6 +414,35 @@ * If you create both a {@code MessageFormat} and {@code ChoiceFormat} * programmatically (instead of using the string patterns), then be careful not to * produce a format that recurses on itself, which will cause an infinite loop. + * + *

Formatting Date and Time

+ * + * MessageFormat provides patterns that support the date/time formatters in the + * {@link java.time.format} and {@link java.text} packages. Consider the following three examples, + * with a date of 11/16/2023: + * + *

1) a date {@code FormatType} with a full {@code FormatStyle}, + * {@snippet lang=java : + * Object[] arg = {new GregorianCalendar(2023, Calendar.NOVEMBER, 16).getTime()}; + * var fmt = new MessageFormat("The date was {0,date,full}"); + * fmt.format(arg); // returns "The date was Thursday, November 16, 2023" + * } + * + *

2) a dtf_date {@code FormatType} with a full {@code FormatStyle}, + * {@snippet lang=java : + * Object[] arg = {LocalDate.of(2023, 11, 16)}; + * var fmt = new MessageFormat("The date was {0,dtf_date,full}"); + * fmt.format(arg); // returns "The date was Thursday, November 16, 2023" + * } + * + *

3) an ISO_LOCAL_DATE {@code FormatType}, + * {@snippet lang=java : + * Object[] arg = {LocalDate.of(2023, 11, 16)}; + * var fmt = new MessageFormat("The date was {0,ISO_LOCAL_DATE}"); + * fmt.format(arg); // returns "The date was 2023-11-16" + * } + * + *

Parsing

*

* When a single argument is parsed more than once in the string, the last match * will be the final result of the parsing. For example, @@ -343,6 +482,7 @@ * @see ChoiceFormat * @see DateFormat * @see SimpleDateFormat + * @see DateTimeFormatter * * @author Mark Davis * @since 1.1 @@ -513,7 +653,7 @@ private void applyPatternImpl(String pattern) { if (braceStack == 0) { part = SEG_RAW; // Set the subformat - makeFormat(i, formatNumber, segments); + setFormatFromPattern(i, formatNumber, segments); formatNumber++; // throw away other segments segments[SEG_INDEX] = null; @@ -549,16 +689,18 @@ private void applyPatternImpl(String pattern) { /** - * Returns a pattern representing the current state of the message format. + * {@return a String pattern adhering to the {@link ##patterns patterns section} that + * represents the current state of this {@code MessageFormat}} + * * The string is constructed from internal information and therefore * does not necessarily equal the previously applied pattern. * * @implSpec The implementation in {@link MessageFormat} returns a * string that, when passed to a {@code MessageFormat()} constructor * or {@link #applyPattern applyPattern()}, produces an instance that - * is semantically equivalent to this instance. - * - * @return a pattern representing the current state of the message format + * is semantically equivalent to this instance. If a subformat cannot be + * converted to a String pattern, the {@code FormatType} and {@code FormatStyle} + * will be omitted from the {@code FormatElement}. */ public String toPattern() { // later, make this more extensible @@ -567,72 +709,77 @@ public String toPattern() { for (int i = 0; i <= maxOffset; ++i) { copyAndFixQuotes(pattern, lastOffset, offsets[i], result); lastOffset = offsets[i]; - result.append('{').append(argumentNumbers[i]); - Format fmt = formats[i]; - String subformatPattern = null; - if (fmt == null) { - // do nothing, string format - } else if (fmt instanceof NumberFormat) { - if (fmt.equals(NumberFormat.getInstance(locale))) { - result.append(",number"); - } else if (fmt.equals(NumberFormat.getCurrencyInstance(locale))) { - result.append(",number,currency"); - } else if (fmt.equals(NumberFormat.getPercentInstance(locale))) { - result.append(",number,percent"); - } else if (fmt.equals(NumberFormat.getIntegerInstance(locale))) { - result.append(",number,integer"); - } else { - if (fmt instanceof DecimalFormat dfmt) { - result.append(",number"); - subformatPattern = dfmt.toPattern(); - } else if (fmt instanceof ChoiceFormat cfmt) { - result.append(",choice"); - subformatPattern = cfmt.toPattern(); - } else { - // UNKNOWN - } - } - } else if (fmt instanceof DateFormat) { - int index; - for (index = MODIFIER_DEFAULT; index < DATE_TIME_MODIFIERS.length; index++) { - DateFormat df = DateFormat.getDateInstance(DATE_TIME_MODIFIERS[index], - locale); - if (fmt.equals(df)) { - result.append(",date"); - break; - } - df = DateFormat.getTimeInstance(DATE_TIME_MODIFIERS[index], - locale); - if (fmt.equals(df)) { - result.append(",time"); - break; - } + result.append('{') + .append(argumentNumbers[i]) + .append(patternFromFormat(formats[i])) + .append('}'); + } + copyAndFixQuotes(pattern, lastOffset, pattern.length(), result); + return result.toString(); + } + + /** + * This method converts a Format into a {@code FormatType} and {@code + * FormatStyle}, if applicable. For each Format, this method will + * first check against the pre-defined styles established in the + * {@link ##patterns patterns section}. Any "default"/"medium" styles + * are omitted according to the specification. + * If a Format does not match to a pre-defined style, it will provide the + * {@code SubformatPattern}, if the Format class can provide one. The + * following subformats do not provide a {@code SubformatPattern}: + * CompactNumberFormat, ListFormat, and DateTimeFormatter (ClassicFormat). + * + *

In addition, since DateTimeFormatter and ClassicFormat do not implement {@code equals()}, + * there is not a means to compare {@code fmt} to a ClassicFormat for equality, + * and thus we don't have enough info to represent it as a pattern since there is no way to check + * if {@code fmt} is equal to some, (for example, "long" style) pre-defined ClassicFormat. + * Even if ClassicFormat implemented equals(), it is a wrapper class for + * DateTimeFormatter, which would require DTF to implement equals() as well to effectively + * compare the two ClassicFormats. + */ + private String patternFromFormat(Format fmt) { + if (fmt instanceof NumberFormat nFmt) { + // Check nFmt factory instances + String nStyle = NumberFormat.matchToStyle(nFmt, locale); + if (nStyle != null) { + return ",number" + (nStyle.isEmpty() ? nStyle : "," + nStyle); + } + // Check SubformatPattern + if (fmt instanceof DecimalFormat dFmt) { + // Quote eligible mFmt pattern characters: '{' and '}' + // Here, and in other subformatPattern instances + return ",number," + copyAndQuoteBraces(dFmt.toPattern()); + } else if (fmt instanceof ChoiceFormat cFmt) { + return ",choice," + copyAndQuoteBraces(cFmt.toPattern()); + } + } else if (fmt instanceof DateFormat) { + // Check dFmt factory instances + for (DateFormat.Style style : DateFormat.Style.values()) { + if (fmt.equals(DateFormat.getDateInstance(style.getValue(), locale))) { + return ",date" + ((style.getValue() != DateFormat.DEFAULT) + ? "," + style.name().toLowerCase(Locale.ROOT) : ""); } - if (index >= DATE_TIME_MODIFIERS.length) { - if (fmt instanceof SimpleDateFormat sdfmt) { - result.append(",date"); - subformatPattern = sdfmt.toPattern(); - } else { - // UNKNOWN - } - } else if (index != MODIFIER_DEFAULT) { - result.append(',').append(DATE_TIME_MODIFIER_KEYWORDS[index]); + if (fmt.equals(DateFormat.getTimeInstance(style.getValue(), locale))) { + return ",time" + ((style.getValue() != DateFormat.DEFAULT) + ? "," + style.name().toLowerCase(Locale.ROOT) : ""); } - } else { - //result.append(", unknown"); } - if (subformatPattern != null) { - result.append(','); - - // The subformat pattern comes already quoted, but only for those characters that are - // special to the subformat. Therefore, we may need to quote additional characters. - // The ones we care about at the MessageFormat level are '{' and '}'. - copyAndQuoteBraces(subformatPattern, result); + // Check SubformatPattern + if (fmt instanceof SimpleDateFormat sdFmt) { + return ",date," + copyAndQuoteBraces(sdFmt.toPattern()); + } + } else if (fmt instanceof ListFormat) { + // Check lFmt factory instances + for (ListFormat.Type type : ListFormat.Type.values()) { + if (fmt.equals(ListFormat.getInstance(locale, type, ListFormat.Style.FULL))) { + return ",list" + ((type != ListFormat.Type.STANDARD) + ? "," + type.name().toLowerCase(Locale.ROOT) : ""); + } } - result.append('}'); } - copyAndFixQuotes(pattern, lastOffset, pattern.length(), result); - return result.toString(); + // By here, this is an instanceof Format that is unknown to MessageFormat. + // Since it is unknown, nothing can be done. + return ""; } /** @@ -694,9 +841,8 @@ public void setFormats(Format[] newFormats) { if (runsToCopy > maxOffset + 1) { runsToCopy = maxOffset + 1; } - for (int i = 0; i < runsToCopy; i++) { - formats[i] = newFormats[i]; - } + if (runsToCopy >= 0) + System.arraycopy(newFormats, 0, formats, 0, runsToCopy); } /** @@ -1063,7 +1209,7 @@ public Object[] parse(String source, ParsePosition pos) { return null; // leave index as is to signal error } else { String strValue= source.substring(sourceOffset,next); - if (!strValue.equals("{"+argumentNumbers[i]+"}")) + if (!strValue.equals("{" + argumentNumbers[i] + "}")) resultArray[argumentNumbers[i]] = source.substring(sourceOffset,next); sourceOffset = next; @@ -1450,65 +1596,21 @@ private void append(StringBuffer result, CharacterIterator iterator) { } // Indices for segments - private static final int SEG_RAW = 0; - private static final int SEG_INDEX = 1; - private static final int SEG_TYPE = 2; - private static final int SEG_MODIFIER = 3; // modifier or subformat - - // Indices for type keywords - private static final int TYPE_NULL = 0; - private static final int TYPE_NUMBER = 1; - private static final int TYPE_DATE = 2; - private static final int TYPE_TIME = 3; - private static final int TYPE_CHOICE = 4; - - private static final String[] TYPE_KEYWORDS = { - "", - "number", - "date", - "time", - "choice" - }; - - // Indices for number modifiers - private static final int MODIFIER_DEFAULT = 0; // common in number and date-time - private static final int MODIFIER_CURRENCY = 1; - private static final int MODIFIER_PERCENT = 2; - private static final int MODIFIER_INTEGER = 3; - - private static final String[] NUMBER_MODIFIER_KEYWORDS = { - "", - "currency", - "percent", - "integer" - }; - - // Indices for date-time modifiers - private static final int MODIFIER_SHORT = 1; - private static final int MODIFIER_MEDIUM = 2; - private static final int MODIFIER_LONG = 3; - private static final int MODIFIER_FULL = 4; - - private static final String[] DATE_TIME_MODIFIER_KEYWORDS = { - "", - "short", - "medium", - "long", - "full" - }; - - // Date-time style values corresponding to the date-time modifiers. - private static final int[] DATE_TIME_MODIFIERS = { - DateFormat.DEFAULT, - DateFormat.SHORT, - DateFormat.MEDIUM, - DateFormat.LONG, - DateFormat.FULL, - }; - - private void makeFormat(int position, int offsetNumber, - StringBuilder[] textSegments) - { + private static final int SEG_RAW = 0; // String in MessageFormatPattern + private static final int SEG_INDEX = 1; // ArgumentIndex + private static final int SEG_TYPE = 2; // FormatType + private static final int SEG_MODIFIER = 3; // FormatStyle + + /** + * This method sets a Format in the {@code formats} array for the + * corresponding {@code argumentNumber} based on the pattern supplied. + * If the pattern supplied does not contain a {@code FormatType}, null + * is stored in the {@code formats} array. + */ + private void setFormatFromPattern(int position, int offsetNumber, + StringBuilder[] textSegments) { + + // Convert any null values in textSegments to empty string String[] segments = new String[textSegments.length]; for (int i = 0; i < textSegments.length; i++) { StringBuilder oneseg = textSegments[i]; @@ -1541,104 +1643,205 @@ private void makeFormat(int position, int offsetNumber, offsets = newOffsets; argumentNumbers = newArgumentNumbers; } + int oldMaxOffset = maxOffset; maxOffset = offsetNumber; offsets[offsetNumber] = segments[SEG_RAW].length(); argumentNumbers[offsetNumber] = argumentNumber; - // now get the format - Format newFormat = null; + // Only search for corresponding type/style if type is not empty if (!segments[SEG_TYPE].isEmpty()) { - int type = findKeyword(segments[SEG_TYPE], TYPE_KEYWORDS); - switch (type) { - case TYPE_NULL: - // Type "" is allowed. e.g., "{0,}", "{0,,}", and "{0,,#}" - // are treated as "{0}". - break; - - case TYPE_NUMBER: - switch (findKeyword(segments[SEG_MODIFIER], NUMBER_MODIFIER_KEYWORDS)) { - case MODIFIER_DEFAULT: - newFormat = NumberFormat.getInstance(locale); - break; - case MODIFIER_CURRENCY: - newFormat = NumberFormat.getCurrencyInstance(locale); - break; - case MODIFIER_PERCENT: - newFormat = NumberFormat.getPercentInstance(locale); - break; - case MODIFIER_INTEGER: - newFormat = NumberFormat.getIntegerInstance(locale); - break; - default: // DecimalFormat pattern - try { - newFormat = new DecimalFormat(segments[SEG_MODIFIER], - DecimalFormatSymbols.getInstance(locale)); - } catch (IllegalArgumentException e) { - maxOffset = oldMaxOffset; - throw e; - } - break; - } - break; - - case TYPE_DATE: - case TYPE_TIME: - int mod = findKeyword(segments[SEG_MODIFIER], DATE_TIME_MODIFIER_KEYWORDS); - if (mod >= 0 && mod < DATE_TIME_MODIFIER_KEYWORDS.length) { - if (type == TYPE_DATE) { - newFormat = DateFormat.getDateInstance(DATE_TIME_MODIFIERS[mod], - locale); - } else { - newFormat = DateFormat.getTimeInstance(DATE_TIME_MODIFIERS[mod], - locale); - } - } else { - // SimpleDateFormat pattern - try { - newFormat = new SimpleDateFormat(segments[SEG_MODIFIER], locale); - } catch (IllegalArgumentException e) { - maxOffset = oldMaxOffset; - throw e; - } - } - break; - - case TYPE_CHOICE: - try { - // ChoiceFormat pattern - newFormat = new ChoiceFormat(segments[SEG_MODIFIER]); - } catch (Exception e) { - maxOffset = oldMaxOffset; - throw new IllegalArgumentException("Choice Pattern incorrect: " - + segments[SEG_MODIFIER], e); - } - break; - - default: + try { + formats[offsetNumber] = formatFromPattern(segments[SEG_TYPE], segments[SEG_MODIFIER]); + } catch (Exception e) { + // Catch to reset maxOffset maxOffset = oldMaxOffset; - throw new IllegalArgumentException("unknown format type: " + - segments[SEG_TYPE]); + throw e; } + } else { + // Type "" is allowed. e.g., "{0,}", "{0,,}", and "{0,,#}" + // are treated as "{0}". + formats[offsetNumber] = null; } - formats[offsetNumber] = newFormat; } - private static int findKeyword(String s, String[] list) { - for (int i = 0; i < list.length; ++i) { - if (s.equals(list[i])) - return i; + /** + * This method converts a {@code FormatType} and {@code FormatStyle} to a + * {@code Format} value. The String parameters are converted + * to their corresponding enum values FormatType and FormatStyle which are used + * to return a {@code Format}. See the patterns section in the class + * description for further detail on a MessageFormat pattern. + * + * @param type the {@code FormatType} in {@code FormatElement} + * @param style the {@code FormatStyle} in {@code FormatElement} + * @return a Format that corresponds to the corresponding {@code formatType} + * and {@code formatStyle} + * @throws IllegalArgumentException if a Format cannot be produced from the + * type and style provided + */ + private Format formatFromPattern(String type, String style) { + // Get the type, if it's valid + FormatType fType; + try { + fType = FormatType.valueOf(type.trim().toUpperCase(Locale.ROOT)); + } catch (IllegalArgumentException iae) { + // Invalid type throws exception + throw new IllegalArgumentException("unknown format type: " + type); + } + // Get the style if recognized, otherwise treat style as a SubformatPattern + FormatStyle fStyle; + try { + fStyle = FormatStyle.fromString(style); + } catch (IllegalArgumentException iae) { + fStyle = FormatStyle.SUBFORMATPATTERN; } + return switch (fType) { + case NUMBER -> switch (fStyle) { + case DEFAULT -> NumberFormat.getInstance(locale); + case CURRENCY -> + NumberFormat.getCurrencyInstance(locale); + case PERCENT -> + NumberFormat.getPercentInstance(locale); + case INTEGER -> + NumberFormat.getIntegerInstance(locale); + case COMPACT_SHORT -> + NumberFormat.getCompactNumberInstance(locale, NumberFormat.Style.SHORT); + case COMPACT_LONG -> + NumberFormat.getCompactNumberInstance(locale, NumberFormat.Style.LONG); + default -> formatFromSubformatPattern(fType, style); + }; + case DATE -> switch (fStyle) { + case DEFAULT -> + DateFormat.getDateInstance(DateFormat.DEFAULT, locale); + case SHORT -> + DateFormat.getDateInstance(DateFormat.SHORT, locale); + case MEDIUM -> + DateFormat.getDateInstance(DateFormat.MEDIUM, locale); + case LONG -> + DateFormat.getDateInstance(DateFormat.LONG, locale); + case FULL -> + DateFormat.getDateInstance(DateFormat.FULL, locale); + default -> formatFromSubformatPattern(fType, style); + }; + case TIME -> switch (fStyle) { + case DEFAULT -> + DateFormat.getTimeInstance(DateFormat.DEFAULT, locale); + case SHORT -> + DateFormat.getTimeInstance(DateFormat.SHORT, locale); + case MEDIUM -> + DateFormat.getTimeInstance(DateFormat.MEDIUM, locale); + case LONG -> + DateFormat.getTimeInstance(DateFormat.LONG, locale); + case FULL -> + DateFormat.getTimeInstance(DateFormat.FULL, locale); + default -> formatFromSubformatPattern(fType, style); + }; + case DTF_DATE -> switch (fStyle) { + case DEFAULT, MEDIUM -> + DateTimeFormatter.ofLocalizedDate(java.time.format.FormatStyle.MEDIUM).withLocale(locale).toFormat(); + case SHORT -> + DateTimeFormatter.ofLocalizedDate(java.time.format.FormatStyle.SHORT).withLocale(locale).toFormat(); + case LONG -> + DateTimeFormatter.ofLocalizedDate(java.time.format.FormatStyle.LONG).withLocale(locale).toFormat(); + case FULL -> + DateTimeFormatter.ofLocalizedDate(java.time.format.FormatStyle.FULL).withLocale(locale).toFormat(); + default -> formatFromSubformatPattern(fType, style); + }; + case DTF_TIME -> switch (fStyle) { + case DEFAULT, MEDIUM -> + DateTimeFormatter.ofLocalizedTime(java.time.format.FormatStyle.MEDIUM).withLocale(locale).toFormat(); + case SHORT -> + DateTimeFormatter.ofLocalizedTime(java.time.format.FormatStyle.SHORT).withLocale(locale).toFormat(); + case LONG -> + DateTimeFormatter.ofLocalizedTime(java.time.format.FormatStyle.LONG).withLocale(locale).toFormat(); + case FULL -> + DateTimeFormatter.ofLocalizedTime(java.time.format.FormatStyle.FULL).withLocale(locale).toFormat(); + default -> formatFromSubformatPattern(fType, style); + }; + case DTF_DATETIME -> switch (fStyle) { + case DEFAULT, MEDIUM -> + DateTimeFormatter.ofLocalizedDateTime(java.time.format.FormatStyle.MEDIUM).withLocale(locale).toFormat(); + case SHORT -> + DateTimeFormatter.ofLocalizedDateTime(java.time.format.FormatStyle.SHORT).withLocale(locale).toFormat(); + case LONG -> + DateTimeFormatter.ofLocalizedDateTime(java.time.format.FormatStyle.LONG).withLocale(locale).toFormat(); + case FULL -> + DateTimeFormatter.ofLocalizedDateTime(java.time.format.FormatStyle.FULL).withLocale(locale).toFormat(); + default -> formatFromSubformatPattern(fType, style); + }; + case CHOICE -> formatFromSubformatPattern(fType, style); + case LIST -> switch (fStyle) { + case DEFAULT -> + ListFormat.getInstance(locale, ListFormat.Type.STANDARD, ListFormat.Style.FULL); + case OR -> + ListFormat.getInstance(locale, ListFormat.Type.OR, ListFormat.Style.FULL); + case UNIT -> + ListFormat.getInstance(locale, ListFormat.Type.UNIT, ListFormat.Style.FULL); + // ListFormat does not provide a String pattern method/constructor + default -> formatFromSubformatPattern(fType, style); + }; + // The DateTimeFormatter constants are only given as a type + // Regardless of style, return the corresponding DTF constant + case BASIC_ISO_DATE -> DateTimeFormatter.BASIC_ISO_DATE.toFormat(); + case ISO_LOCAL_DATE -> DateTimeFormatter.ISO_LOCAL_DATE.toFormat(); + case ISO_OFFSET_DATE -> DateTimeFormatter.ISO_OFFSET_DATE.toFormat(); + case ISO_DATE -> DateTimeFormatter.ISO_DATE.toFormat(); + case ISO_LOCAL_TIME -> DateTimeFormatter.ISO_LOCAL_TIME.toFormat(); + case ISO_OFFSET_TIME -> DateTimeFormatter.ISO_OFFSET_TIME.toFormat(); + case ISO_TIME -> DateTimeFormatter.ISO_TIME.toFormat(); + case ISO_LOCAL_DATE_TIME -> DateTimeFormatter.ISO_LOCAL_DATE_TIME.toFormat(); + case ISO_OFFSET_DATE_TIME -> DateTimeFormatter.ISO_OFFSET_DATE_TIME.toFormat(); + case ISO_ZONED_DATE_TIME -> DateTimeFormatter.ISO_ZONED_DATE_TIME.toFormat(); + case ISO_DATE_TIME -> DateTimeFormatter.ISO_DATE_TIME.toFormat(); + case ISO_ORDINAL_DATE -> DateTimeFormatter.ISO_ORDINAL_DATE.toFormat(); + case ISO_WEEK_DATE -> DateTimeFormatter.ISO_WEEK_DATE.toFormat(); + case ISO_INSTANT -> DateTimeFormatter.ISO_INSTANT.toFormat(); + case RFC_1123_DATE_TIME -> DateTimeFormatter.RFC_1123_DATE_TIME.toFormat(); + }; + } - // Try trimmed lowercase. - String ls = s.trim().toLowerCase(Locale.ROOT); - if (ls != s) { - for (int i = 0; i < list.length; ++i) { - if (ls.equals(list[i])) - return i; + /** + * This method will attempt to return a subformat produced with the provided + * SubformatPattern applied. If the subformat does not support SubformatPatterns + * or the SubformatPattern is illegal to the subformat, an IllegalArgumentException + * is thrown. To adhere to the specification, this method ensures if an underlying + * exception is thrown, it is rethrown as an IllegalArgumentException unless + * the underlying exception is itself an IAE, or an NPE. + * + * @param fType the enum type of the subformat + * @param pattern the SubformatPattern to be applied + * @return a Format that corresponds to the corresponding {@code fType} + * and {@code pattern} + * @throws IllegalArgumentException if a Format cannot be produced from the + * type and SubformatPattern provided + */ + private Format formatFromSubformatPattern(FormatType fType, String pattern) { + // Modified for neater exception value if needed + String type = fType.name().charAt(0) + fType.name().substring(1).toLowerCase(Locale.ROOT); + try { + return switch (fType) { + case NUMBER -> new DecimalFormat(pattern, DecimalFormatSymbols.getInstance(locale)); + case DATE, TIME -> new SimpleDateFormat(pattern, locale); + case DTF_DATE, DTF_TIME, DTF_DATETIME -> + DateTimeFormatter.ofPattern(pattern).toFormat(); + case CHOICE -> new ChoiceFormat(pattern); + // These classe(s) do not support String patterns + default -> throw new IllegalArgumentException(String.format( + "Unexpected modifier for %s: %s", type, pattern)); + }; + } catch (Exception e) { + // getClass check over separate catch block to not catch the IAE subclasses + // For example, ChoiceFormat can throw a NumberFormatException + if (e.getClass() == IllegalArgumentException.class + || e.getClass() == NullPointerException.class) { + // If IAE no need to wrap with another IAE + // If NPE, it should be thrown as is (as specified) + throw e; + } else { + throw new IllegalArgumentException(String.format( + "%s pattern incorrect: %s", type, pattern), e); } } - return -1; } private static void copyAndFixQuotes(String source, int start, int end, @@ -1668,14 +1871,17 @@ private static void copyAndFixQuotes(String source, int start, int end, } } - // Copy the text, but add quotes around any quotables that aren't already quoted - private static void copyAndQuoteBraces(String source, StringBuilder target) { + // The subformat pattern comes already quoted, but only for those characters that are + // special to the subformat. Therefore, we may need to quote additional characters. + // The ones we care about at the MessageFormat level are '{' and '}'. + private static String copyAndQuoteBraces(String source) { // Analyze existing string for already quoted and newly quotable characters record Qchar(char ch, boolean quoted) { }; ArrayList qchars = new ArrayList<>(); boolean quoted = false; boolean anyChangeNeeded = false; + StringBuilder quotedSource = new StringBuilder(); for (int i = 0; i < source.length(); i++) { char ch = source.charAt(i); if (ch == '\'') { @@ -1694,8 +1900,7 @@ record Qchar(char ch, boolean quoted) { }; // Was any change needed? if (!anyChangeNeeded) { - target.append(source); - return; + return source; } // Build new string, automatically consolidating adjacent runs of quoted chars @@ -1703,15 +1908,85 @@ record Qchar(char ch, boolean quoted) { }; for (Qchar qchar : qchars) { char ch = qchar.ch; if (ch == '\'') { - target.append(ch); // doubling works whether quoted or not + quotedSource.append(ch); // doubling works whether quoted or not } else if (qchar.quoted() != quoted) { - target.append('\''); + quotedSource.append('\''); quoted = qchar.quoted(); } - target.append(ch); + quotedSource.append(ch); } if (quoted) { - target.append('\''); + quotedSource.append('\''); + } + + return quotedSource.toString(); + } + + // Corresponding to the FormatType pattern + private enum FormatType { + NUMBER, + DATE, + TIME, + DTF_DATE, + DTF_TIME, + DTF_DATETIME, + CHOICE, + LIST, + + // Pre-defined DateTimeFormatter types + BASIC_ISO_DATE, + ISO_LOCAL_DATE, + ISO_OFFSET_DATE , + ISO_DATE, + ISO_LOCAL_TIME, + ISO_OFFSET_TIME, + ISO_TIME, + ISO_LOCAL_DATE_TIME, + ISO_OFFSET_DATE_TIME, + ISO_ZONED_DATE_TIME, + ISO_DATE_TIME, + ISO_ORDINAL_DATE, + ISO_WEEK_DATE, + ISO_INSTANT, + RFC_1123_DATE_TIME; + } + + // Corresponding to the FormatStyle pattern + private enum FormatStyle { + DEFAULT(""), + SHORT("short"), + MEDIUM("medium"), + LONG("long"), + FULL("full"), + INTEGER("integer"), + CURRENCY("currency"), + PERCENT("percent"), + COMPACT_SHORT("compact_short"), + COMPACT_LONG("compact_long"), + OR("or"), + UNIT("unit"), + SUBFORMATPATTERN(null); + + private final String text; + + // Differs from FormatType in that the text String is + // not guaranteed to match the Enum name, thus a text field is used + FormatStyle(String text) { + this.text = text; + } + + // This method returns a FormatStyle (excluding SUBFORMATPATTERN) + // that matches the passed String. If no FormatStyle is found, + // an IllegalArgumentException is thrown + private static FormatStyle fromString(String text) { + for (FormatStyle style : values()) { + // Also check trimmed case-insensitive for historical reasons + if (style != FormatStyle.SUBFORMATPATTERN && + text.trim().compareToIgnoreCase(style.text) == 0) { + return style; + } + } + throw new IllegalArgumentException(); } } diff --git a/src/java.base/share/classes/java/text/NumberFormat.java b/src/java.base/share/classes/java/text/NumberFormat.java index 2f448950bea66..716cfb710c453 100644 --- a/src/java.base/share/classes/java/text/NumberFormat.java +++ b/src/java.base/share/classes/java/text/NumberFormat.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1996, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1996, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -677,6 +677,33 @@ public static NumberFormat getCompactNumberInstance(Locale locale, return getInstance(locale, formatStyle, COMPACTSTYLE); } + /** + * This method compares the passed NumberFormat to a number of pre-defined + * style NumberFormat instances, (created with the passed locale). Returns a + * matching FormatStyle string if found, otherwise null. + * This method is used by MessageFormat to provide string pattens for NumberFormat + * Subformats. Any future pre-defined NumberFormat styles should be added to this method. + */ + static String matchToStyle(NumberFormat fmt, Locale locale) { + if (fmt.equals(NumberFormat.getInstance(locale))) { + return ""; + } else if (fmt.equals(NumberFormat.getCurrencyInstance(locale))) { + return "currency"; + } else if (fmt.equals(NumberFormat.getPercentInstance(locale))) { + return "percent"; + } else if (fmt.equals(NumberFormat.getIntegerInstance(locale))) { + return "integer"; + } else if (fmt.equals(NumberFormat.getCompactNumberInstance(locale, + NumberFormat.Style.SHORT))) { + return "compact_short"; + } else if (fmt.equals(NumberFormat.getCompactNumberInstance(locale, + NumberFormat.Style.LONG))) { + return "compact_long"; + } else { + return null; + } + } + /** * Returns an array of all locales for which the * {@code get*Instance} methods of this class can return diff --git a/test/jdk/java/text/Format/MessageFormat/CompactSubFormats.java b/test/jdk/java/text/Format/MessageFormat/CompactSubFormats.java new file mode 100644 index 0000000000000..f4b12a14fcc97 --- /dev/null +++ b/test/jdk/java/text/Format/MessageFormat/CompactSubFormats.java @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * 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 8318761 + * @summary Test MessageFormatPattern ability to recognize and produce + * appropriate FormatType and FormatStyle for CompactNumberFormat. + * @run junit CompactSubFormats + */ + +import java.text.CompactNumberFormat; +import java.text.DecimalFormat; +import java.text.DecimalFormatSymbols; +import java.text.MessageFormat; +import java.text.NumberFormat; +import java.util.Locale; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class CompactSubFormats { + + // Ensure the built-in FormatType and FormatStyles for cnFmt are as expected + @Test + public void applyPatternTest() { + var mFmt = new MessageFormat( + "{0,number,compact_short}{1,number,compact_long}"); + var compactShort = NumberFormat.getCompactNumberInstance( + mFmt.getLocale(), NumberFormat.Style.SHORT); + var compactLong = NumberFormat.getCompactNumberInstance( + mFmt.getLocale(), NumberFormat.Style.LONG); + assertEquals(mFmt.getFormatsByArgumentIndex()[0], compactShort); + assertEquals(mFmt.getFormatsByArgumentIndex()[1], compactLong); + } + + // Ensure that only 'compact_short' and 'compact_long' are recognized as + // compact number modifiers. All other compact_XX should be interpreted as + // a subformatPattern for a DecimalFormat + @Test + public void recognizedCompactStylesTest() { + // An exception won't be thrown since 'compact_regular' will be interpreted as a + // subformatPattern. + assertEquals(new DecimalFormat("compact_regular"), + new MessageFormat("{0,number,compact_regular}").getFormatsByArgumentIndex()[0]); + } + + // SHORT and LONG CompactNumberFormats should produce correct patterns + @Test + public void toPatternTest() { + var mFmt = new MessageFormat("{0}{1}"); + mFmt.setFormatByArgumentIndex(0, NumberFormat.getCompactNumberInstance( + mFmt.getLocale(), NumberFormat.Style.SHORT)); + mFmt.setFormatByArgumentIndex(1, NumberFormat.getCompactNumberInstance( + mFmt.getLocale(), NumberFormat.Style.LONG)); + assertEquals("{0,number,compact_short}{1,number,compact_long}", mFmt.toPattern()); + } + + // A custom cnFmt cannot be recognized, thus does not produce any built-in pattern + @Test + public void badToPatternTest() { + var mFmt = new MessageFormat("{0}"); + // Non-recognizable compactNumberFormat + mFmt.setFormatByArgumentIndex(0, new CompactNumberFormat("", + DecimalFormatSymbols.getInstance(Locale.US), new String[]{""})); + // Default behavior of unrecognizable Formats is a FormatElement + // in the form of { ArgumentIndex } + assertEquals("{0}", mFmt.toPattern()); + } +} diff --git a/test/jdk/java/text/Format/MessageFormat/ListSubFormats.java b/test/jdk/java/text/Format/MessageFormat/ListSubFormats.java new file mode 100644 index 0000000000000..598239ef5ef71 --- /dev/null +++ b/test/jdk/java/text/Format/MessageFormat/ListSubFormats.java @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * 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 8318761 + * @summary Test MessageFormatPattern ability to recognize and produce the + * appropriate FormatType and FormatStyle for ListFormat. ListFormat's + * STANDARD, OR, and UNIT types are supported as built-in patterns for + * MessageFormat. All types use the FULL style. + * @run junit ListSubFormats + */ + +import java.text.ListFormat; +import java.text.MessageFormat; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +public class ListSubFormats { + + // Recognize the 'list' FormatType as well as '', 'or', and + // 'unit' associated FormatStyles + @Test + public void applyPatternTest() { + var mFmt = new MessageFormat("{0,list}{1,list,or}{2,list,unit}"); + var listStandard = ListFormat.getInstance(mFmt.getLocale(), + ListFormat.Type.STANDARD, ListFormat.Style.FULL); + var listOr = ListFormat.getInstance(mFmt.getLocale(), + ListFormat.Type.OR, ListFormat.Style.FULL); + var listUnit = ListFormat.getInstance(mFmt.getLocale(), + ListFormat.Type.UNIT, ListFormat.Style.FULL); + assertEquals(mFmt.getFormatsByArgumentIndex()[0], listStandard); + assertEquals(mFmt.getFormatsByArgumentIndex()[1], listOr); + assertEquals(mFmt.getFormatsByArgumentIndex()[2], listUnit); + } + + // Ensure incorrect FormatElement pattern throws IAE + // java.text.ListFormat does not support String subformatPatterns + @Test + public void badApplyPatternTest() { + // Wrong FormatStyle + IllegalArgumentException exc = assertThrows(IllegalArgumentException.class, () -> + new MessageFormat("{0,list,standard}")); + assertEquals("Unexpected modifier for List: standard", exc.getMessage()); + + // Wrong FormatType + exc = assertThrows(IllegalArgumentException.class, () -> + new MessageFormat("{0,listt,or}")); + assertEquals("unknown format type: listt", exc.getMessage()); + + } + + // STANDARD, OR, UNIT ListFormats (with FULL style) should + // produce correct patterns. + @Test + public void toPatternTest() { + var mFmt = new MessageFormat("{0}{1}{2}"); + mFmt.setFormatByArgumentIndex(0, + ListFormat.getInstance(mFmt.getLocale(), ListFormat.Type.STANDARD, ListFormat.Style.FULL)); + mFmt.setFormatByArgumentIndex(1, + ListFormat.getInstance(mFmt.getLocale(), ListFormat.Type.OR, ListFormat.Style.FULL)); + mFmt.setFormatByArgumentIndex(2, + ListFormat.getInstance(mFmt.getLocale(), ListFormat.Type.UNIT, ListFormat.Style.FULL)); + assertEquals("{0,list}{1,list,or}{2,list,unit}", mFmt.toPattern()); + } + + // A custom ListFormat cannot be recognized, thus does not produce any built-in pattern + @Test + public void badToPatternTest() { + var mFmt = new MessageFormat("{0}"); + mFmt.setFormatByArgumentIndex(0, + ListFormat.getInstance(mFmt.getLocale(), ListFormat.Type.UNIT, ListFormat.Style.NARROW)); + assertEquals("{0}", mFmt.toPattern()); + } +} diff --git a/test/jdk/java/text/Format/MessageFormat/MessageFormatExceptions.java b/test/jdk/java/text/Format/MessageFormat/MessageFormatExceptions.java index f28c3170e71aa..276f49a07d7b8 100644 --- a/test/jdk/java/text/Format/MessageFormat/MessageFormatExceptions.java +++ b/test/jdk/java/text/Format/MessageFormat/MessageFormatExceptions.java @@ -24,7 +24,7 @@ /* * @test * @summary Validate some exceptions in MessageFormat - * @bug 6481179 8039165 + * @bug 6481179 8039165 8318761 * @run junit MessageFormatExceptions */ @@ -39,6 +39,15 @@ public class MessageFormatExceptions { + // Any exception for a Subformat should be re-thrown as propagated as an IAE + // to the MessageFormat + @Test + public void rethrowAsIAE() { + // Same Subformat pattern for ChoiceFormat throws NumberFormatException + assertThrows(IllegalArgumentException.class, + () -> new MessageFormat("{0,choice,0foo#foo}")); + } + // MessageFormat should throw NPE when constructed with a null pattern @Test public void nullPatternTest() { @@ -57,6 +66,9 @@ public void nullLocaleTest() { // Fails when constructor invokes applyPattern() assertThrows(NullPointerException.class, () -> new MessageFormat("{0, date}", null)); + // Same as above, but with Subformat pattern + assertThrows(NullPointerException.class, + () -> new MessageFormat("{0, date,dd}", null)); // Fail when constructor invokes applyPattern() assertThrows(NullPointerException.class, () -> new MessageFormat("{0, number}", null)); diff --git a/test/jdk/java/text/Format/MessageFormat/TemporalSubFormats.java b/test/jdk/java/text/Format/MessageFormat/TemporalSubFormats.java new file mode 100644 index 0000000000000..c8572551a8d50 --- /dev/null +++ b/test/jdk/java/text/Format/MessageFormat/TemporalSubFormats.java @@ -0,0 +1,204 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * 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 8318761 + * @summary Test MessageFormatPattern ability to recognize the appropriate + * FormatType and FormatStyle for DateTimeFormatter(ClassicFormat). + * This includes the types dtf_time, dtf_date, dtf_datetime, + * and the DateTimeFormatter predefined formatters. + * @run junit TemporalSubFormats + */ + +import java.text.Format; +import java.text.MessageFormat; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.OffsetDateTime; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.time.format.FormatStyle; +import java.util.stream.Stream; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +public class TemporalSubFormats { + + // Check that applying the built-in DateTimeFormatter types returns the + // correct Format and formats properly. Patterns are case-insensitive + @ParameterizedTest + @MethodSource("preDefinedTypes") + public void preDefinedPatternsTest(String pattern, Format fmt) { + var mFmt = new MessageFormat("quux{0,"+pattern+"}quux"); + Object[] temporals = new Object[]{LocalDate.now(), LocalTime.now(), + ZonedDateTime.now(), LocalDateTime.now(), OffsetDateTime.now(), Instant.now()}; + for (Object val : temporals) { + // Wrap in Object array for MessageFormat + Object[] wrappedVal = new Object[]{val}; + + try { + String mFmtted = mFmt.format(wrappedVal); + // If current format can support the time object. Check equality of result + assertEquals(mFmtted, "quux"+fmt.format(val)+"quux"); + } catch (IllegalArgumentException ignored) { + // Otherwise, ensure both throw IAE on unsupported field + assertThrows(IllegalArgumentException.class, () -> fmt.format(val)); + } + } + } + + // Provides String patterns and the associated (standalone) FormatType + // Values should be case-insensitive + private static Stream preDefinedTypes() { + return Stream.of( + Arguments.of("BASIC_ISO_DATE", DateTimeFormatter.BASIC_ISO_DATE.toFormat()), + Arguments.of("ISO_LOCAL_DATE", DateTimeFormatter.ISO_LOCAL_DATE.toFormat()), + Arguments.of("ISO_OFFSET_DATE", DateTimeFormatter.ISO_OFFSET_DATE.toFormat()), + Arguments.of("ISO_DATE", DateTimeFormatter.ISO_DATE.toFormat()), + Arguments.of("iso_local_time", DateTimeFormatter.ISO_LOCAL_TIME.toFormat()), + Arguments.of("ISO_OFFSET_TIME", DateTimeFormatter.ISO_OFFSET_TIME.toFormat()), + Arguments.of("iso_time", DateTimeFormatter.ISO_TIME.toFormat()), + Arguments.of("ISO_LOCAL_DATE_TIME", DateTimeFormatter.ISO_LOCAL_DATE_TIME.toFormat()), + Arguments.of("ISO_OFFSET_DATE_TIME", DateTimeFormatter.ISO_OFFSET_DATE_TIME.toFormat()), + Arguments.of("ISO_ZONED_DATE_TIME", DateTimeFormatter.ISO_ZONED_DATE_TIME.toFormat()), + Arguments.of("ISO_DATE_TIME", DateTimeFormatter.ISO_DATE_TIME.toFormat()), + Arguments.of("ISO_ORDINAL_DATE", DateTimeFormatter.ISO_ORDINAL_DATE.toFormat()), + Arguments.of("iso_week_date", DateTimeFormatter.ISO_WEEK_DATE.toFormat()), + Arguments.of("ISO_INSTANT", DateTimeFormatter.ISO_INSTANT.toFormat()), + Arguments.of("RFC_1123_DATE_TIME", DateTimeFormatter.RFC_1123_DATE_TIME.toFormat()) + ); + } + + // Check that the appropriate FormatType/Style combo returns correct Format + // Unlike the other pattern tests, the formatted output is used to check + // equality, as DateTimeFormatter does not implement equals() + @ParameterizedTest + @MethodSource("styles") + public void applyPatternTest(String style, FormatStyle fStyle) { + var time = ZonedDateTime.now(); + var date = LocalDate.now(); + + // Test dtf_date + var dFmt = new MessageFormat("{0,dtf_date"+style+"}"); + assertEquals(DateTimeFormatter.ofLocalizedDate(fStyle).withLocale( + dFmt.getLocale()).toFormat().format(date), + dFmt.getFormatsByArgumentIndex()[0].format(date)); + + // Test dtf_time + var tFmt = new MessageFormat("{0,dtf_time"+style+"}"); + assertEquals(DateTimeFormatter.ofLocalizedTime(fStyle).withLocale( + tFmt.getLocale()).toFormat().format(time), + tFmt.getFormatsByArgumentIndex()[0].format(time)); + + // Test dtf_datetime + var dtFmt = new MessageFormat("{0,dtf_datetime"+style+"}"); + assertEquals(DateTimeFormatter.ofLocalizedDateTime(fStyle).withLocale( + dtFmt.getLocale()).toFormat().format(time), + dtFmt.getFormatsByArgumentIndex()[0].format(time)); + } + + // Provides String patterns and the associated FormatStyle + private static Stream styles() { + return Stream.of( + Arguments.of("", FormatStyle.MEDIUM), + Arguments.of(",short", FormatStyle.SHORT), + Arguments.of(",medium", FormatStyle.MEDIUM), + Arguments.of(",long", FormatStyle.LONG), + Arguments.of(",full", FormatStyle.FULL) + ); + } + + // Test that a proper Format from a SubformatPattern can be reproduced + @Test + public void subformatPatternTest() { + // SubformatPattern invokes the same method for both dtf_date, + // dtf_time, and dtf_datetime + var pattern = "d MMM uuuu"; + var date = LocalDate.now(); + + // Test dtf_date + var dFmt = new MessageFormat("{0,dtf_date,"+pattern+"}"); + assertEquals(DateTimeFormatter.ofPattern(pattern,dFmt.getLocale()).toFormat().format(date), + dFmt.getFormatsByArgumentIndex()[0].format(date)); + + // Test dtf_time + var tFmt = new MessageFormat("{0,dtf_time,"+pattern+"}"); + assertEquals(DateTimeFormatter.ofPattern(pattern,tFmt.getLocale()).toFormat().format(date), + tFmt.getFormatsByArgumentIndex()[0].format(date)); + + // Test dtf_datetime + var dtFmt = new MessageFormat("{0,dtf_datetime,"+pattern+"}"); + assertEquals(DateTimeFormatter.ofPattern(pattern,dtFmt.getLocale()).toFormat().format(date), + dtFmt.getFormatsByArgumentIndex()[0].format(date)); + } + + // Ensure that only the supported built-in FormatStyles or a + // valid SubformatPattern are recognized + @Test + public void badApplyPatternTest() { + // Not a supported FormatStyle: throws the underlying IAE from DTF + // as it is interpreted as a subformatPattern + IllegalArgumentException exc = assertThrows(IllegalArgumentException.class, () -> + new MessageFormat("{0,dtf_date,longer}")); + assertEquals("Unknown pattern letter: l", exc.getMessage()); + + // Not a legal SubformatPattern: throws the underlying IAE from DTF + exc = assertThrows(IllegalArgumentException.class, () -> + new MessageFormat("{0,dtf_date,VVV}")); + assertEquals("Pattern letter count must be 2: V", exc.getMessage()); + + // Pre-defined ISO style does not exist and should be ignored + assertDoesNotThrow(() -> new MessageFormat("{0,BASIC_ISO_DATE,foo}"), + "Style on a pre-defined DTF should be ignored, instead of throwing an exception"); + } + + // DateTimeFormatters cannot be recognized when toPattern() is invoked + // Default behavior of unrecognizable Formats is a FormatElement + // in the form of { ArgumentIndex } + @Test + public void nonRecognizableToPatternTest() { + // Check SubformatPattern + var validPattern = "yy"; + var mFmt = new MessageFormat("{0}"); + mFmt.setFormatByArgumentIndex(0, DateTimeFormatter.ofPattern(validPattern).toFormat()); + assertEquals("{0}", mFmt.toPattern()); + + // Check pre-defined styles + var dFmt = new MessageFormat("{0,dtf_date,long}"); + assertEquals("{0}", dFmt.toPattern()); + var tFmt = new MessageFormat("{0,dtf_time,long}"); + assertEquals("{0}", tFmt.toPattern()); + var dtFmt = new MessageFormat("{0,dtf_datetime,long}"); + assertEquals("{0}", dtFmt.toPattern()); + } +} From 54f09d734584a71c648520664447f8395050adbe Mon Sep 17 00:00:00 2001 From: Jaikiran Pai Date: Fri, 23 Feb 2024 00:53:19 +0000 Subject: [PATCH 25/38] 8278527: java/util/concurrent/tck/JSR166TestCase.java fails nanoTime test Reviewed-by: martin, lancea --- .../util/concurrent/tck/JSR166TestCase.java | 1 - .../java/util/concurrent/tck/SystemTest.java | 75 ------------------- 2 files changed, 76 deletions(-) delete mode 100644 test/jdk/java/util/concurrent/tck/SystemTest.java diff --git a/test/jdk/java/util/concurrent/tck/JSR166TestCase.java b/test/jdk/java/util/concurrent/tck/JSR166TestCase.java index 76ab189e553fc..cb1c4ec4ed3cf 100644 --- a/test/jdk/java/util/concurrent/tck/JSR166TestCase.java +++ b/test/jdk/java/util/concurrent/tck/JSR166TestCase.java @@ -603,7 +603,6 @@ public static Test suite() { ScheduledExecutorSubclassTest.suite(), SemaphoreTest.suite(), SynchronousQueueTest.suite(), - SystemTest.suite(), ThreadLocalTest.suite(), ThreadPoolExecutorTest.suite(), ThreadPoolExecutorSubclassTest.suite(), diff --git a/test/jdk/java/util/concurrent/tck/SystemTest.java b/test/jdk/java/util/concurrent/tck/SystemTest.java deleted file mode 100644 index c34bdabea31c4..0000000000000 --- a/test/jdk/java/util/concurrent/tck/SystemTest.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -/* - * This file is available under and governed by the GNU General Public - * License version 2 only, as published by the Free Software Foundation. - * However, the following notice accompanied the original version of this - * file: - * - * Written by Doug Lea with assistance from members of JCP JSR-166 - * Expert Group and released to the public domain, as explained at - * http://creativecommons.org/publicdomain/zero/1.0/ - * Other contributors include Andrew Wright, Jeffrey Hayes, - * Pat Fisher, Mike Judd. - */ - -import junit.framework.Test; -import junit.framework.TestSuite; - -public class SystemTest extends JSR166TestCase { - public static void main(String[] args) { - main(suite(), args); - } - - public static Test suite() { - return new TestSuite(SystemTest.class); - } - - /** - * Worst case rounding for millisecs; set for 60 cycle millis clock. - * This value might need to be changed on JVMs with coarser - * System.currentTimeMillis clocks. - */ - static final long MILLIS_ROUND = 17; - - /** - * Nanos between readings of millis is no longer than millis (plus - * possible rounding), and vice versa. - * This shows only that nano timing not (much) worse than milli. - */ - public void testNanoTime() throws InterruptedException { - long m0 = System.currentTimeMillis(); - long n0 = System.nanoTime(); - Thread.sleep(1); - long m1 = System.currentTimeMillis(); - long n1 = System.nanoTime(); - Thread.sleep(50); // avoid possibly scaled SHORT_DELAY_MS - long m2 = System.currentTimeMillis(); - long n2 = System.nanoTime(); - Thread.sleep(1); - long m3 = System.currentTimeMillis(); - long n3 = System.nanoTime(); - assertTrue((n2 - n1) / 1_000_000 <= m3 - m0 + MILLIS_ROUND); - assertTrue(m2 - m1 <= (n3 - n0) / 1_000_000 + MILLIS_ROUND); - } -} From c4409eafc418c1e7a4ca2a2a522b6855c70c0f8c Mon Sep 17 00:00:00 2001 From: Erik Gahlin Date: Fri, 23 Feb 2024 02:02:35 +0000 Subject: [PATCH 26/38] 8325994: JFR: Examples in JFR.start help use incorrect separator Reviewed-by: mikael --- .../share/classes/jdk/jfr/internal/dcmd/DCmdStart.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/dcmd/DCmdStart.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/dcmd/DCmdStart.java index a7e423e549aa9..6fb6866cdff11 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/dcmd/DCmdStart.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/dcmd/DCmdStart.java @@ -418,9 +418,9 @@ Virtual Machine (JVM) shuts down. If set to 'true' and no value $ jcmd JFR.start filename=dump.jfr $ jcmd JFR.start filename=%s $ jcmd JFR.start dumponexit=true - $ jcmd JFR.start maxage=1h,maxsize=1000M + $ jcmd JFR.start maxage=1h maxsize=1000M $ jcmd JFR.start settings=profile - $ jcmd JFR.start delay=5m,settings=my.jfc + $ jcmd JFR.start delay=5m settings=my.jfc $ jcmd JFR.start gc=high method-profiling=high $ jcmd JFR.start jdk.JavaMonitorEnter#threshold=1ms $ jcmd JFR.start +HelloWorld#enabled=true +HelloWorld#stackTrace=true From cb809f8e04c12f0d06237c9c3fd05f6c585098a6 Mon Sep 17 00:00:00 2001 From: Jan Lahoda Date: Fri, 23 Feb 2024 08:56:14 +0000 Subject: [PATCH 27/38] 8325215: Incorrect not exhaustive switch error Reviewed-by: vromero --- .../com/sun/tools/javac/comp/Flow.java | 49 ++++++++++- .../tools/javac/patterns/Exhaustiveness.java | 82 ++++++++++++++++++- 2 files changed, 129 insertions(+), 2 deletions(-) diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Flow.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Flow.java index 83bcf999da5d2..98a060d6bf89c 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Flow.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Flow.java @@ -815,6 +815,7 @@ private boolean exhausts(JCExpression selector, List cases) { } } Set patterns = patternSet; + boolean genericPatternsExpanded = false; try { boolean repeat = true; while (repeat) { @@ -824,10 +825,22 @@ private boolean exhausts(JCExpression selector, List cases) { updatedPatterns = reduceRecordPatterns(updatedPatterns); updatedPatterns = removeCoveredRecordPatterns(updatedPatterns); repeat = !updatedPatterns.equals(patterns); - patterns = updatedPatterns; if (checkCovered(selector.type, patterns)) { return true; } + if (!repeat && !genericPatternsExpanded) { + //there may be situation like: + //class B extends S1, S2 + //patterns: R(S1, B), R(S2, S2) + //this should be joined to R(B, S2), + //but hashing in reduceNestedPatterns will not allow that + //attempt to once expand all types to their transitive permitted types, + //on all depth of nesting: + updatedPatterns = expandGenericPatterns(updatedPatterns); + genericPatternsExpanded = true; + repeat = !updatedPatterns.equals(patterns); + } + patterns = updatedPatterns; } return checkCovered(selector.type, patterns); } catch (CompletionFailure cf) { @@ -1130,6 +1143,40 @@ private PatternDescription reduceRecordPattern(PatternDescription pattern) { return pattern; } + private Set expandGenericPatterns(Set patterns) { + var newPatterns = new HashSet(patterns); + boolean modified; + do { + modified = false; + for (PatternDescription pd : patterns) { + if (pd instanceof RecordPattern rpOne) { + for (int i = 0; i < rpOne.nested.length; i++) { + Set toExpand = Set.of(rpOne.nested[i]); + Set expanded = expandGenericPatterns(toExpand); + if (expanded != toExpand) { + expanded.removeAll(toExpand); + for (PatternDescription exp : expanded) { + PatternDescription[] newNested = Arrays.copyOf(rpOne.nested, rpOne.nested.length); + newNested[i] = exp; + modified |= newPatterns.add(new RecordPattern(rpOne.recordType(), rpOne.fullComponentTypes(), newNested)); + } + } + } + } else if (pd instanceof BindingPattern bp) { + Set permittedSymbols = allPermittedSubTypes((ClassSymbol) bp.type.tsym, cs -> true); + + if (!permittedSymbols.isEmpty()) { + for (Symbol permitted : permittedSymbols) { + //TODO infer.instantiatePatternType(selectorType, csym); (?) + modified |= newPatterns.add(new BindingPattern(permitted.type)); + } + } + } + } + } while (modified); + return newPatterns; + } + private Set removeCoveredRecordPatterns(Set patterns) { Set existingBindings = patterns.stream() .filter(pd -> pd instanceof BindingPattern) diff --git a/test/langtools/tools/javac/patterns/Exhaustiveness.java b/test/langtools/tools/javac/patterns/Exhaustiveness.java index dad1a0c86f8d7..726bd1d9a2e9a 100644 --- a/test/langtools/tools/javac/patterns/Exhaustiveness.java +++ b/test/langtools/tools/javac/patterns/Exhaustiveness.java @@ -23,7 +23,7 @@ /** * @test - * @bug 8262891 8268871 8274363 8281100 8294670 8311038 8311815 + * @bug 8262891 8268871 8274363 8281100 8294670 8311038 8311815 8325215 * @summary Check exhaustiveness of switches over sealed types. * @library /tools/lib * @modules jdk.compiler/com.sun.tools.javac.api @@ -1996,6 +1996,86 @@ case Pair(D fst, D snd) -> { """); } + @Test //JDK-8325215: + public void testTooGenericPatternInRecord(Path base) throws Exception { + doTest(base, + new String[0], + """ + package test; + public class Test { + sealed interface A permits T, U {} + sealed interface B permits V, W {} + + static final class T implements A { public T() {} } + static final class U implements A { public U() {} } + + static final class V implements B { public V() {} } + static final class W implements B { public W() {} } + + final static record R(A a, B b) { } + + static int r(R r) { + return switch (r) { + case R(A a, V b) -> 1; // Any A with specific B + case R(T a, B b) -> 2; // Specific A with any B + case R(U a, W b) -> 3; // Specific A with specific B + }; + } + } + """); + doTest(base, + new String[0], + """ + package test; + public class Test { + sealed interface A permits T, U {} + sealed interface B permits V, W {} + + static final class T implements A { public T() {} } + static final class U implements A { public U() {} } + + static final class V implements B { public V() {} } + static final class W implements B { public W() {} } + + final static record R(B b, A a) { } + + static int r(R r) { + return switch (r) { + case R(V b, A a) -> 1; // Any A with specific B + case R(B b, T a) -> 2; // Specific A with any B + case R(W b, U a) -> 3; // Specific A with specific B + }; + } + } + """); + doTest(base, + new String[0], + """ + package test; + public class Test { + sealed interface A permits T, U {} + sealed interface B permits V, W {} + + static final class T implements A { public T() {} } + static final class U implements A { public U() {} } + + static final class V implements B { public V() {} } + static final class W implements B { public W() {} } + + final static record X(B b) { } + final static record R(A a, X x) { } + + static int r(R r) { + return switch (r) { + case R(A a, X(V b)) -> 1; // Any A with specific B + case R(T a, X(B b)) -> 2; // Specific A with any B + case R(U a, X(W b)) -> 3; // Specific A with specific B + }; + } + } + """); + } + private void doTest(Path base, String[] libraryCode, String testCode, String... expectedErrors) throws IOException { doTest(base, libraryCode, testCode, false, expectedErrors); } From 336bbbe3895214a772e0f3aafb36277c46645ded Mon Sep 17 00:00:00 2001 From: Roman Kennke Date: Fri, 23 Feb 2024 10:05:25 +0000 Subject: [PATCH 28/38] 8139457: Relax alignment of array elements Co-authored-by: Fei Yang Co-authored-by: Thomas Stuefe Reviewed-by: stuefe, stefank, shade, coleenp, kdnilsen, aboldtch --- .../cpu/aarch64/c1_LIRAssembler_aarch64.cpp | 2 +- .../cpu/aarch64/c1_MacroAssembler_aarch64.cpp | 15 ++- .../cpu/aarch64/c1_MacroAssembler_aarch64.hpp | 4 +- src/hotspot/cpu/arm/c1_LIRAssembler_arm.cpp | 2 +- src/hotspot/cpu/ppc/c1_LIRAssembler_ppc.cpp | 4 +- src/hotspot/cpu/ppc/c1_MacroAssembler_ppc.cpp | 20 +++- src/hotspot/cpu/ppc/c1_MacroAssembler_ppc.hpp | 4 +- .../cpu/riscv/c1_LIRAssembler_riscv.cpp | 4 +- .../cpu/riscv/c1_MacroAssembler_riscv.cpp | 19 ++- .../cpu/riscv/c1_MacroAssembler_riscv.hpp | 4 +- src/hotspot/cpu/s390/c1_LIRAssembler_s390.cpp | 2 +- .../cpu/s390/c1_MacroAssembler_s390.cpp | 12 +- .../cpu/s390/c1_MacroAssembler_s390.hpp | 4 +- src/hotspot/cpu/x86/c1_LIRAssembler_x86.cpp | 4 +- src/hotspot/cpu/x86/c1_MacroAssembler_x86.cpp | 18 ++- src/hotspot/cpu/x86/c1_MacroAssembler_x86.hpp | 4 +- src/hotspot/share/gc/shared/collectedHeap.cpp | 13 +- src/hotspot/share/gc/x/xObjArrayAllocator.cpp | 14 ++- src/hotspot/share/gc/z/zObjArrayAllocator.cpp | 12 +- src/hotspot/share/jvmci/jvmciCompilerToVM.cpp | 2 +- src/hotspot/share/oops/arrayOop.hpp | 65 +++++----- src/hotspot/share/oops/objArrayOop.hpp | 39 +----- src/hotspot/share/opto/runtime.cpp | 15 ++- src/hotspot/share/opto/type.cpp | 9 +- src/hotspot/share/prims/unsafe.cpp | 4 +- .../sun/jvm/hotspot/memory/Universe.java | 9 +- .../classes/sun/jvm/hotspot/oops/Array.java | 39 +++--- test/hotspot/gtest/oops/test_arrayOop.cpp | 56 +++++++-- test/hotspot/gtest/oops/test_objArrayOop.cpp | 57 +++++++++ test/hotspot/jtreg/gtest/ArrayTests.java | 56 +++++++++ test/hotspot/jtreg/gtest/ObjArrayTests.java | 85 +++++++++++++ .../runtime/FieldLayout/ArrayBaseOffsets.java | 113 ++++++++++++++++++ .../GetObjectSizeIntrinsicsTest.java | 21 ++-- 33 files changed, 560 insertions(+), 171 deletions(-) create mode 100644 test/hotspot/gtest/oops/test_objArrayOop.cpp create mode 100644 test/hotspot/jtreg/gtest/ArrayTests.java create mode 100644 test/hotspot/jtreg/gtest/ObjArrayTests.java create mode 100644 test/hotspot/jtreg/runtime/FieldLayout/ArrayBaseOffsets.java diff --git a/src/hotspot/cpu/aarch64/c1_LIRAssembler_aarch64.cpp b/src/hotspot/cpu/aarch64/c1_LIRAssembler_aarch64.cpp index ba613b62a3e2a..9bd8c6b8e9f88 100644 --- a/src/hotspot/cpu/aarch64/c1_LIRAssembler_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/c1_LIRAssembler_aarch64.cpp @@ -1209,7 +1209,7 @@ void LIR_Assembler::emit_alloc_array(LIR_OpAllocArray* op) { len, tmp1, tmp2, - arrayOopDesc::header_size(op->type()), + arrayOopDesc::base_offset_in_bytes(op->type()), array_element_size(op->type()), op->klass()->as_register(), *op->stub()->entry()); diff --git a/src/hotspot/cpu/aarch64/c1_MacroAssembler_aarch64.cpp b/src/hotspot/cpu/aarch64/c1_MacroAssembler_aarch64.cpp index 50d35f281e5a3..e48d64d90696c 100644 --- a/src/hotspot/cpu/aarch64/c1_MacroAssembler_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/c1_MacroAssembler_aarch64.cpp @@ -188,6 +188,12 @@ void C1_MacroAssembler::initialize_header(Register obj, Register klass, Register if (len->is_valid()) { strw(len, Address(obj, arrayOopDesc::length_offset_in_bytes())); + int base_offset = arrayOopDesc::length_offset_in_bytes() + BytesPerInt; + if (!is_aligned(base_offset, BytesPerWord)) { + assert(is_aligned(base_offset, BytesPerInt), "must be 4-byte aligned"); + // Clear gap/first 4 bytes following the length field. + strw(zr, Address(obj, base_offset)); + } } else if (UseCompressedClassPointers) { store_klass_gap(obj, zr); } @@ -266,7 +272,7 @@ void C1_MacroAssembler::initialize_object(Register obj, Register klass, Register verify_oop(obj); } -void C1_MacroAssembler::allocate_array(Register obj, Register len, Register t1, Register t2, int header_size, int f, Register klass, Label& slow_case) { +void C1_MacroAssembler::allocate_array(Register obj, Register len, Register t1, Register t2, int base_offset_in_bytes, int f, Register klass, Label& slow_case) { assert_different_registers(obj, len, t1, t2, klass); // determine alignment mask @@ -279,7 +285,7 @@ void C1_MacroAssembler::allocate_array(Register obj, Register len, Register t1, const Register arr_size = t2; // okay to be the same // align object end - mov(arr_size, (int32_t)header_size * BytesPerWord + MinObjAlignmentInBytesMask); + mov(arr_size, (int32_t)base_offset_in_bytes + MinObjAlignmentInBytesMask); add(arr_size, arr_size, len, ext::uxtw, f); andr(arr_size, arr_size, ~MinObjAlignmentInBytesMask); @@ -287,8 +293,11 @@ void C1_MacroAssembler::allocate_array(Register obj, Register len, Register t1, initialize_header(obj, klass, len, t1, t2); + // Align-up to word boundary, because we clear the 4 bytes potentially + // following the length field in initialize_header(). + int base_offset = align_up(base_offset_in_bytes, BytesPerWord); // clear rest of allocated space - initialize_body(obj, arr_size, header_size * BytesPerWord, t1, t2); + initialize_body(obj, arr_size, base_offset, t1, t2); if (Compilation::current()->bailed_out()) { return; } diff --git a/src/hotspot/cpu/aarch64/c1_MacroAssembler_aarch64.hpp b/src/hotspot/cpu/aarch64/c1_MacroAssembler_aarch64.hpp index 4aa6206aa6073..d210c21d12b8f 100644 --- a/src/hotspot/cpu/aarch64/c1_MacroAssembler_aarch64.hpp +++ b/src/hotspot/cpu/aarch64/c1_MacroAssembler_aarch64.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1999, 2024, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2014, 2021, Red Hat Inc. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -100,7 +100,7 @@ using MacroAssembler::null_check; // header_size: size of object header in words // f : element scale factor // slow_case : exit to slow case implementation if fast allocation fails - void allocate_array(Register obj, Register len, Register t, Register t2, int header_size, int f, Register klass, Label& slow_case); + void allocate_array(Register obj, Register len, Register t, Register t2, int base_offset_in_bytes, int f, Register klass, Label& slow_case); int rsp_offset() const { return _rsp_offset; } void set_rsp_offset(int n) { _rsp_offset = n; } diff --git a/src/hotspot/cpu/arm/c1_LIRAssembler_arm.cpp b/src/hotspot/cpu/arm/c1_LIRAssembler_arm.cpp index 16aeaa20c04b8..688790f07e548 100644 --- a/src/hotspot/cpu/arm/c1_LIRAssembler_arm.cpp +++ b/src/hotspot/cpu/arm/c1_LIRAssembler_arm.cpp @@ -968,7 +968,7 @@ void LIR_Assembler::emit_alloc_array(LIR_OpAllocArray* op) { op->tmp1()->as_register(), op->tmp2()->as_register(), op->tmp3()->as_register(), - arrayOopDesc::header_size(op->type()), + arrayOopDesc::base_offset_in_bytes(op->type()), type2aelembytes(op->type()), op->klass()->as_register(), *op->stub()->entry()); diff --git a/src/hotspot/cpu/ppc/c1_LIRAssembler_ppc.cpp b/src/hotspot/cpu/ppc/c1_LIRAssembler_ppc.cpp index 4b29bcf57e4d3..3ae35949b2148 100644 --- a/src/hotspot/cpu/ppc/c1_LIRAssembler_ppc.cpp +++ b/src/hotspot/cpu/ppc/c1_LIRAssembler_ppc.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 2024, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2012, 2023 SAP SE. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -2298,7 +2298,7 @@ void LIR_Assembler::emit_alloc_array(LIR_OpAllocArray* op) { op->tmp1()->as_register(), op->tmp2()->as_register(), op->tmp3()->as_register(), - arrayOopDesc::header_size(op->type()), + arrayOopDesc::base_offset_in_bytes(op->type()), type2aelembytes(op->type()), op->klass()->as_register(), *op->stub()->entry()); diff --git a/src/hotspot/cpu/ppc/c1_MacroAssembler_ppc.cpp b/src/hotspot/cpu/ppc/c1_MacroAssembler_ppc.cpp index b379d4141a32b..ba0187d0363ca 100644 --- a/src/hotspot/cpu/ppc/c1_MacroAssembler_ppc.cpp +++ b/src/hotspot/cpu/ppc/c1_MacroAssembler_ppc.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1999, 2024, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2012, 2018 SAP SE. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -310,7 +310,7 @@ void C1_MacroAssembler::allocate_array( Register t1, // temp register Register t2, // temp register Register t3, // temp register - int hdr_size, // object header size in words + int base_offset_in_bytes, // elements offset in bytes int elt_size, // element size in bytes Register klass, // object klass Label& slow_case // continuation point if fast allocation fails @@ -342,7 +342,7 @@ void C1_MacroAssembler::allocate_array( sldi(t1, len, log2_elt_size); arr_len_in_bytes = t1; } - addi(arr_size, arr_len_in_bytes, hdr_size * wordSize + MinObjAlignmentInBytesMask); // Add space for header & alignment. + addi(arr_size, arr_len_in_bytes, base_offset_in_bytes + MinObjAlignmentInBytesMask); // Add space for header & alignment. clrrdi(arr_size, arr_size, LogMinObjAlignmentInBytes); // Align array size. // Allocate space & initialize header. @@ -352,8 +352,18 @@ void C1_MacroAssembler::allocate_array( // Initialize body. const Register base = t2; const Register index = t3; - addi(base, obj, hdr_size * wordSize); // compute address of first element - addi(index, arr_size, -(hdr_size * wordSize)); // compute index = number of bytes to clear + addi(base, obj, base_offset_in_bytes); // compute address of first element + addi(index, arr_size, -(base_offset_in_bytes)); // compute index = number of bytes to clear + + // Zero first 4 bytes, if start offset is not word aligned. + if (!is_aligned(base_offset_in_bytes, BytesPerWord)) { + assert(is_aligned(base_offset_in_bytes, BytesPerInt), "must be 4-byte aligned"); + li(t1, 0); + stw(t1, 0, base); + addi(base, base, BytesPerInt); + // Note: initialize_body will align index down, no need to correct it here. + } + initialize_body(base, index); if (CURRENT_ENV->dtrace_alloc_probes()) { diff --git a/src/hotspot/cpu/ppc/c1_MacroAssembler_ppc.hpp b/src/hotspot/cpu/ppc/c1_MacroAssembler_ppc.hpp index 5fa19d5fd5dad..ab31431e67d9c 100644 --- a/src/hotspot/cpu/ppc/c1_MacroAssembler_ppc.hpp +++ b/src/hotspot/cpu/ppc/c1_MacroAssembler_ppc.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1999, 2024, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2012, 2015 SAP SE. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -80,7 +80,7 @@ Register t1, // temp register Register t2, // temp register Register t3, // temp register - int hdr_size, // object header size in words + int base_offset_in_bytes, // elements offset in bytes int elt_size, // element size in bytes Register klass, // object klass Label& slow_case // continuation point if fast allocation fails diff --git a/src/hotspot/cpu/riscv/c1_LIRAssembler_riscv.cpp b/src/hotspot/cpu/riscv/c1_LIRAssembler_riscv.cpp index e3ec023aef260..5d0fa3fad3cec 100644 --- a/src/hotspot/cpu/riscv/c1_LIRAssembler_riscv.cpp +++ b/src/hotspot/cpu/riscv/c1_LIRAssembler_riscv.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 2024, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2014, 2020, Red Hat Inc. All rights reserved. * Copyright (c) 2020, 2023, Huawei Technologies Co., Ltd. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. @@ -1020,7 +1020,7 @@ void LIR_Assembler::emit_alloc_array(LIR_OpAllocArray* op) { len, tmp1, tmp2, - arrayOopDesc::header_size(op->type()), + arrayOopDesc::base_offset_in_bytes(op->type()), array_element_size(op->type()), op->klass()->as_register(), *op->stub()->entry()); diff --git a/src/hotspot/cpu/riscv/c1_MacroAssembler_riscv.cpp b/src/hotspot/cpu/riscv/c1_MacroAssembler_riscv.cpp index 2961b1a91ceab..770dd6a9d0f37 100644 --- a/src/hotspot/cpu/riscv/c1_MacroAssembler_riscv.cpp +++ b/src/hotspot/cpu/riscv/c1_MacroAssembler_riscv.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1999, 2024, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2014, Red Hat Inc. All rights reserved. * Copyright (c) 2020, 2022, Huawei Technologies Co., Ltd. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. @@ -280,7 +280,7 @@ void C1_MacroAssembler::initialize_object(Register obj, Register klass, Register verify_oop(obj); } -void C1_MacroAssembler::allocate_array(Register obj, Register len, Register tmp1, Register tmp2, int header_size, int f, Register klass, Label& slow_case) { +void C1_MacroAssembler::allocate_array(Register obj, Register len, Register tmp1, Register tmp2, int base_offset_in_bytes, int f, Register klass, Label& slow_case) { assert_different_registers(obj, len, tmp1, tmp2, klass); // determine alignment mask @@ -292,7 +292,7 @@ void C1_MacroAssembler::allocate_array(Register obj, Register len, Register tmp1 const Register arr_size = tmp2; // okay to be the same // align object end - mv(arr_size, (int32_t)header_size * BytesPerWord + MinObjAlignmentInBytesMask); + mv(arr_size, (int32_t)base_offset_in_bytes + MinObjAlignmentInBytesMask); shadd(arr_size, len, arr_size, t0, f); andi(arr_size, arr_size, ~(uint)MinObjAlignmentInBytesMask); @@ -300,9 +300,20 @@ void C1_MacroAssembler::allocate_array(Register obj, Register len, Register tmp1 initialize_header(obj, klass, len, tmp1, tmp2); + // Clear leading 4 bytes, if necessary. + // TODO: This could perhaps go into initialize_body() and also clear the leading 4 bytes + // for non-array objects, thereby replacing the klass-gap clearing code in initialize_header(). + int base_offset = base_offset_in_bytes; + if (!is_aligned(base_offset, BytesPerWord)) { + assert(is_aligned(base_offset, BytesPerInt), "must be 4-byte aligned"); + sw(zr, Address(obj, base_offset)); + base_offset += BytesPerInt; + } + assert(is_aligned(base_offset, BytesPerWord), "must be word-aligned"); + // clear rest of allocated space const Register len_zero = len; - initialize_body(obj, arr_size, header_size * BytesPerWord, len_zero); + initialize_body(obj, arr_size, base_offset, len_zero); membar(MacroAssembler::StoreStore); diff --git a/src/hotspot/cpu/riscv/c1_MacroAssembler_riscv.hpp b/src/hotspot/cpu/riscv/c1_MacroAssembler_riscv.hpp index b737a438511c8..2d7f8d7485d4f 100644 --- a/src/hotspot/cpu/riscv/c1_MacroAssembler_riscv.hpp +++ b/src/hotspot/cpu/riscv/c1_MacroAssembler_riscv.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1999, 2024, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2014, 2015, Red Hat Inc. All rights reserved. * Copyright (c) 2020, 2022, Huawei Technologies Co., Ltd. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. @@ -101,7 +101,7 @@ using MacroAssembler::null_check; // header_size: size of object header in words // f : element scale factor // slow_case : exit to slow case implementation if fast allocation fails - void allocate_array(Register obj, Register len, Register tmp1, Register tmp2, int header_size, int f, Register klass, Label& slow_case); + void allocate_array(Register obj, Register len, Register tmp1, Register tmp2, int base_offset_in_bytes, int f, Register klass, Label& slow_case); int rsp_offset() const { return _rsp_offset; } diff --git a/src/hotspot/cpu/s390/c1_LIRAssembler_s390.cpp b/src/hotspot/cpu/s390/c1_LIRAssembler_s390.cpp index 13c45bb9fe708..503440a5fcc01 100644 --- a/src/hotspot/cpu/s390/c1_LIRAssembler_s390.cpp +++ b/src/hotspot/cpu/s390/c1_LIRAssembler_s390.cpp @@ -2382,7 +2382,7 @@ void LIR_Assembler::emit_alloc_array(LIR_OpAllocArray* op) { op->len()->as_register(), op->tmp1()->as_register(), op->tmp2()->as_register(), - arrayOopDesc::header_size(op->type()), + arrayOopDesc::base_offset_in_bytes(op->type()), type2aelembytes(op->type()), op->klass()->as_register(), *op->stub()->entry()); diff --git a/src/hotspot/cpu/s390/c1_MacroAssembler_s390.cpp b/src/hotspot/cpu/s390/c1_MacroAssembler_s390.cpp index 5dddc7a756f4c..58bdcee5d5f8f 100644 --- a/src/hotspot/cpu/s390/c1_MacroAssembler_s390.cpp +++ b/src/hotspot/cpu/s390/c1_MacroAssembler_s390.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2024, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2016, 2023 SAP SE. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -271,7 +271,7 @@ void C1_MacroAssembler::allocate_array( Register len, // array length Register t1, // temp register Register t2, // temp register - int hdr_size, // object header size in words + int base_offset_in_bytes, // elements offset in bytes int elt_size, // element size in bytes Register klass, // object klass Label& slow_case // Continuation point if fast allocation fails. @@ -297,8 +297,8 @@ void C1_MacroAssembler::allocate_array( case 8: z_sllg(arr_size, len, 3); break; default: ShouldNotReachHere(); } - add2reg(arr_size, hdr_size * wordSize + MinObjAlignmentInBytesMask); // Add space for header & alignment. - z_nill(arr_size, (~MinObjAlignmentInBytesMask) & 0xffff); // Align array size. + add2reg(arr_size, base_offset_in_bytes + MinObjAlignmentInBytesMask); // Add space for header & alignment. + z_nill(arr_size, (~MinObjAlignmentInBytesMask) & 0xffff); // Align array size. try_allocate(obj, arr_size, 0, t1, slow_case); @@ -308,9 +308,9 @@ void C1_MacroAssembler::allocate_array( Label done; Register object_fields = t1; Register Rzero = Z_R1_scratch; - z_aghi(arr_size, -(hdr_size * BytesPerWord)); + z_aghi(arr_size, -base_offset_in_bytes); z_bre(done); // Jump if size of fields is zero. - z_la(object_fields, hdr_size * BytesPerWord, obj); + z_la(object_fields, base_offset_in_bytes, obj); z_xgr(Rzero, Rzero); initialize_body(object_fields, arr_size, Rzero); bind(done); diff --git a/src/hotspot/cpu/s390/c1_MacroAssembler_s390.hpp b/src/hotspot/cpu/s390/c1_MacroAssembler_s390.hpp index 7a4f76af1546e..c77258509e1a5 100644 --- a/src/hotspot/cpu/s390/c1_MacroAssembler_s390.hpp +++ b/src/hotspot/cpu/s390/c1_MacroAssembler_s390.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2024, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2016, 2023 SAP SE. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -86,7 +86,7 @@ Register len, // array length Register t1, // temp register Register t2, // temp register - int hdr_size, // object header size in words + int base_offset_in_bytes, // elements offset in bytes int elt_size, // element size in bytes Register klass, // object klass Label& slow_case // Continuation point if fast allocation fails. diff --git a/src/hotspot/cpu/x86/c1_LIRAssembler_x86.cpp b/src/hotspot/cpu/x86/c1_LIRAssembler_x86.cpp index 3b7a3cec2d815..c279e3073af87 100644 --- a/src/hotspot/cpu/x86/c1_LIRAssembler_x86.cpp +++ b/src/hotspot/cpu/x86/c1_LIRAssembler_x86.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -1618,7 +1618,7 @@ void LIR_Assembler::emit_alloc_array(LIR_OpAllocArray* op) { len, tmp1, tmp2, - arrayOopDesc::header_size(op->type()), + arrayOopDesc::base_offset_in_bytes(op->type()), array_element_size(op->type()), op->klass()->as_register(), *op->stub()->entry()); diff --git a/src/hotspot/cpu/x86/c1_MacroAssembler_x86.cpp b/src/hotspot/cpu/x86/c1_MacroAssembler_x86.cpp index 0c4544f5bc49e..caca3a1528261 100644 --- a/src/hotspot/cpu/x86/c1_MacroAssembler_x86.cpp +++ b/src/hotspot/cpu/x86/c1_MacroAssembler_x86.cpp @@ -186,6 +186,15 @@ void C1_MacroAssembler::initialize_header(Register obj, Register klass, Register if (len->is_valid()) { movl(Address(obj, arrayOopDesc::length_offset_in_bytes()), len); +#ifdef _LP64 + int base_offset = arrayOopDesc::length_offset_in_bytes() + BytesPerInt; + if (!is_aligned(base_offset, BytesPerWord)) { + assert(is_aligned(base_offset, BytesPerInt), "must be 4-byte aligned"); + // Clear gap/first 4 bytes following the length field. + xorl(t1, t1); + movl(Address(obj, base_offset), t1); + } +#endif } #ifdef _LP64 else if (UseCompressedClassPointers) { @@ -269,7 +278,7 @@ void C1_MacroAssembler::initialize_object(Register obj, Register klass, Register verify_oop(obj); } -void C1_MacroAssembler::allocate_array(Register obj, Register len, Register t1, Register t2, int header_size, Address::ScaleFactor f, Register klass, Label& slow_case) { +void C1_MacroAssembler::allocate_array(Register obj, Register len, Register t1, Register t2, int base_offset_in_bytes, Address::ScaleFactor f, Register klass, Label& slow_case) { assert(obj == rax, "obj must be in rax, for cmpxchg"); assert_different_registers(obj, len, t1, t2, klass); @@ -282,7 +291,7 @@ void C1_MacroAssembler::allocate_array(Register obj, Register len, Register t1, const Register arr_size = t2; // okay to be the same // align object end - movptr(arr_size, header_size * BytesPerWord + MinObjAlignmentInBytesMask); + movptr(arr_size, base_offset_in_bytes + MinObjAlignmentInBytesMask); lea(arr_size, Address(arr_size, len, f)); andptr(arr_size, ~MinObjAlignmentInBytesMask); @@ -292,7 +301,10 @@ void C1_MacroAssembler::allocate_array(Register obj, Register len, Register t1, // clear rest of allocated space const Register len_zero = len; - initialize_body(obj, arr_size, header_size * BytesPerWord, len_zero); + // Align-up to word boundary, because we clear the 4 bytes potentially + // following the length field in initialize_header(). + int base_offset = align_up(base_offset_in_bytes, BytesPerWord); + initialize_body(obj, arr_size, base_offset, len_zero); if (CURRENT_ENV->dtrace_alloc_probes()) { assert(obj == rax, "must be"); diff --git a/src/hotspot/cpu/x86/c1_MacroAssembler_x86.hpp b/src/hotspot/cpu/x86/c1_MacroAssembler_x86.hpp index b3593feb05640..ae340e64fb737 100644 --- a/src/hotspot/cpu/x86/c1_MacroAssembler_x86.hpp +++ b/src/hotspot/cpu/x86/c1_MacroAssembler_x86.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1999, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -89,7 +89,7 @@ // header_size: size of object header in words // f : element scale factor // slow_case : exit to slow case implementation if fast allocation fails - void allocate_array(Register obj, Register len, Register t, Register t2, int header_size, Address::ScaleFactor f, Register klass, Label& slow_case); + void allocate_array(Register obj, Register len, Register t, Register t2, int base_offset_in_bytes, Address::ScaleFactor f, Register klass, Label& slow_case); int rsp_offset() const { return _rsp_offset; } void set_rsp_offset(int n) { _rsp_offset = n; } diff --git a/src/hotspot/share/gc/shared/collectedHeap.cpp b/src/hotspot/share/gc/shared/collectedHeap.cpp index dbcef1ba575a9..02d20b92b5ce7 100644 --- a/src/hotspot/share/gc/shared/collectedHeap.cpp +++ b/src/hotspot/share/gc/shared/collectedHeap.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2001, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2001, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -402,6 +402,13 @@ void CollectedHeap::set_gc_cause(GCCause::Cause v) { _gc_cause = v; } +// Returns the header size in words aligned to the requirements of the +// array object type. +static int int_array_header_size() { + size_t typesize_in_bytes = arrayOopDesc::header_size_in_bytes(); + return (int)align_up(typesize_in_bytes, HeapWordSize)/HeapWordSize; +} + size_t CollectedHeap::max_tlab_size() const { // TLABs can't be bigger than we can fill with a int[Integer.MAX_VALUE]. // This restriction could be removed by enabling filling with multiple arrays. @@ -411,14 +418,14 @@ size_t CollectedHeap::max_tlab_size() const { // We actually lose a little by dividing first, // but that just makes the TLAB somewhat smaller than the biggest array, // which is fine, since we'll be able to fill that. - size_t max_int_size = typeArrayOopDesc::header_size(T_INT) + + size_t max_int_size = int_array_header_size() + sizeof(jint) * ((juint) max_jint / (size_t) HeapWordSize); return align_down(max_int_size, MinObjAlignment); } size_t CollectedHeap::filler_array_hdr_size() { - return align_object_offset(arrayOopDesc::header_size(T_INT)); // align to Long + return align_object_offset(int_array_header_size()); // align to Long } size_t CollectedHeap::filler_array_min_size() { diff --git a/src/hotspot/share/gc/x/xObjArrayAllocator.cpp b/src/hotspot/share/gc/x/xObjArrayAllocator.cpp index 9408e027cbd8c..0950b886a9b7b 100644 --- a/src/hotspot/share/gc/x/xObjArrayAllocator.cpp +++ b/src/hotspot/share/gc/x/xObjArrayAllocator.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -50,7 +50,17 @@ oop XObjArrayAllocator::initialize(HeapWord* mem) const { // time and time-to-safepoint const size_t segment_max = XUtils::bytes_to_words(64 * K); const BasicType element_type = ArrayKlass::cast(_klass)->element_type(); - const size_t header = arrayOopDesc::header_size(element_type); + + // Clear leading 32 bits, if necessary. + int base_offset = arrayOopDesc::base_offset_in_bytes(element_type); + if (!is_aligned(base_offset, HeapWordSize)) { + assert(is_aligned(base_offset, BytesPerInt), "array base must be 32 bit aligned"); + *reinterpret_cast(reinterpret_cast(mem) + base_offset) = 0; + base_offset += BytesPerInt; + } + assert(is_aligned(base_offset, HeapWordSize), "remaining array base must be 64 bit aligned"); + + const size_t header = heap_word_size(base_offset); const size_t payload_size = _word_size - header; if (payload_size <= segment_max) { diff --git a/src/hotspot/share/gc/z/zObjArrayAllocator.cpp b/src/hotspot/share/gc/z/zObjArrayAllocator.cpp index faa1290f37e84..b5aaed3a27411 100644 --- a/src/hotspot/share/gc/z/zObjArrayAllocator.cpp +++ b/src/hotspot/share/gc/z/zObjArrayAllocator.cpp @@ -50,7 +50,17 @@ oop ZObjArrayAllocator::initialize(HeapWord* mem) const { // time and time-to-safepoint const size_t segment_max = ZUtils::bytes_to_words(64 * K); const BasicType element_type = ArrayKlass::cast(_klass)->element_type(); - const size_t header = arrayOopDesc::header_size(element_type); + + // Clear leading 32 bits, if necessary. + int base_offset = arrayOopDesc::base_offset_in_bytes(element_type); + if (!is_aligned(base_offset, HeapWordSize)) { + assert(is_aligned(base_offset, BytesPerInt), "array base must be 32 bit aligned"); + *reinterpret_cast(reinterpret_cast(mem) + base_offset) = 0; + base_offset += BytesPerInt; + } + assert(is_aligned(base_offset, HeapWordSize), "remaining array base must be 64 bit aligned"); + + const size_t header = heap_word_size(base_offset); const size_t payload_size = _word_size - header; if (payload_size <= segment_max) { diff --git a/src/hotspot/share/jvmci/jvmciCompilerToVM.cpp b/src/hotspot/share/jvmci/jvmciCompilerToVM.cpp index ca3750733131a..efc47def0214e 100644 --- a/src/hotspot/share/jvmci/jvmciCompilerToVM.cpp +++ b/src/hotspot/share/jvmci/jvmciCompilerToVM.cpp @@ -2442,7 +2442,7 @@ C2V_END C2V_VMENTRY_0(jint, arrayBaseOffset, (JNIEnv* env, jobject, jchar type_char)) BasicType type = JVMCIENV->typeCharToBasicType(type_char, JVMCI_CHECK_0); - return arrayOopDesc::header_size(type) * HeapWordSize; + return arrayOopDesc::base_offset_in_bytes(type); C2V_END C2V_VMENTRY_0(jint, arrayIndexScale, (JNIEnv* env, jobject, jchar type_char)) diff --git a/src/hotspot/share/oops/arrayOop.hpp b/src/hotspot/share/oops/arrayOop.hpp index a557268fe7772..0aa26500bd8d2 100644 --- a/src/hotspot/share/oops/arrayOop.hpp +++ b/src/hotspot/share/oops/arrayOop.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -27,6 +27,7 @@ #include "oops/oop.hpp" #include "utilities/align.hpp" +#include "utilities/globalDefinitions.hpp" // arrayOopDesc is the abstract baseclass for all arrays. It doesn't // declare pure virtual to enforce this because that would allocate a vtbl @@ -45,13 +46,29 @@ class arrayOopDesc : public oopDesc { // Interpreter/Compiler offsets +private: + // Returns the address of the length "field". See length_offset_in_bytes(). + static int* length_addr_impl(void* obj_ptr) { + char* ptr = static_cast(obj_ptr); + return reinterpret_cast(ptr + length_offset_in_bytes()); + } + + // Given a type, return true if elements of that type must be aligned to 64-bit. + static bool element_type_should_be_aligned(BasicType type) { +#ifdef _LP64 + if (type == T_OBJECT || type == T_ARRAY) { + return !UseCompressedOops; + } +#endif + return type == T_DOUBLE || type == T_LONG; + } + + public: // Header size computation. // The header is considered the oop part of this type plus the length. - // Returns the aligned header_size_in_bytes. This is not equivalent to - // sizeof(arrayOopDesc) which should not appear in the code. + // This is not equivalent to sizeof(arrayOopDesc) which should not appear in the code. static int header_size_in_bytes() { - size_t hs = align_up(length_offset_in_bytes() + sizeof(int), - HeapWordSize); + size_t hs = length_offset_in_bytes() + sizeof(int); #ifdef ASSERT // make sure it isn't called before UseCompressedOops is initialized. static size_t arrayoopdesc_hs = 0; @@ -61,20 +78,6 @@ class arrayOopDesc : public oopDesc { return (int)hs; } - // Returns the address of the length "field". See length_offset_in_bytes(). - static int* length_addr_impl(void* obj_ptr) { - char* ptr = static_cast(obj_ptr); - return reinterpret_cast(ptr + length_offset_in_bytes()); - } - - // Check whether an element of a typeArrayOop with the given type must be - // aligned 0 mod 8. The typeArrayOop itself must be aligned at least this - // strongly. - static bool element_type_should_be_aligned(BasicType type) { - return type == T_DOUBLE || type == T_LONG; - } - - public: // The _length field is not declared in C++. It is allocated after the // declared nonstatic fields in arrayOopDesc if not compressed, otherwise // it occupies the second half of the _klass field in oopDesc. @@ -85,7 +88,8 @@ class arrayOopDesc : public oopDesc { // Returns the offset of the first element. static int base_offset_in_bytes(BasicType type) { - return header_size(type) * HeapWordSize; + size_t hs = header_size_in_bytes(); + return (int)(element_type_should_be_aligned(type) ? align_up(hs, BytesPerLong) : hs); } // Returns the address of the first element. The elements in the array will not @@ -122,18 +126,7 @@ class arrayOopDesc : public oopDesc { *length_addr_impl(mem) = length; } - // Should only be called with constants as argument - // (will not constant fold otherwise) - // Returns the header size in words aligned to the requirements of the - // array object type. - static int header_size(BasicType type) { - size_t typesize_in_bytes = header_size_in_bytes(); - return (int)(element_type_should_be_aligned(type) - ? align_object_offset(typesize_in_bytes/HeapWordSize) - : typesize_in_bytes/HeapWordSize); - } - - // Return the maximum length of an array of BasicType. The length can passed + // Return the maximum length of an array of BasicType. The length can be passed // to typeArrayOop::object_size(scale, length, header_size) without causing an // overflow. We also need to make sure that this will not overflow a size_t on // 32 bit platforms when we convert it to a byte size. @@ -141,8 +134,12 @@ class arrayOopDesc : public oopDesc { assert(type < T_CONFLICT, "wrong type"); assert(type2aelembytes(type) != 0, "wrong type"); + size_t hdr_size_in_bytes = base_offset_in_bytes(type); + // This is rounded-up and may overlap with the first array elements. + size_t hdr_size_in_words = align_up(hdr_size_in_bytes, HeapWordSize) / HeapWordSize; + const size_t max_element_words_per_size_t = - align_down((SIZE_MAX/HeapWordSize - header_size(type)), MinObjAlignment); + align_down((SIZE_MAX/HeapWordSize - hdr_size_in_words), MinObjAlignment); const size_t max_elements_per_size_t = HeapWordSize * max_element_words_per_size_t / type2aelembytes(type); if ((size_t)max_jint < max_elements_per_size_t) { @@ -150,7 +147,7 @@ class arrayOopDesc : public oopDesc { // (CollectedHeap, Klass::oop_oop_iterate(), and more) uses an int for // passing around the size (in words) of an object. So, we need to avoid // overflowing an int when we add the header. See CRs 4718400 and 7110613. - return align_down(max_jint - header_size(type), MinObjAlignment); + return align_down(max_jint - hdr_size_in_words, MinObjAlignment); } return (int32_t)max_elements_per_size_t; } diff --git a/src/hotspot/share/oops/objArrayOop.hpp b/src/hotspot/share/oops/objArrayOop.hpp index de6d4d3d042ba..20e2953fee9f5 100644 --- a/src/hotspot/share/oops/objArrayOop.hpp +++ b/src/hotspot/share/oops/objArrayOop.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -51,32 +51,6 @@ class objArrayOopDesc : public arrayOopDesc { return base_offset_in_bytes() + sizeof(T) * index; } -private: - // Give size of objArrayOop in HeapWords minus the header - static int array_size(int length) { - const uint OopsPerHeapWord = HeapWordSize/heapOopSize; - assert(OopsPerHeapWord >= 1 && (HeapWordSize % heapOopSize == 0), - "Else the following (new) computation would be in error"); - uint res = ((uint)length + OopsPerHeapWord - 1)/OopsPerHeapWord; -#ifdef ASSERT - // The old code is left in for sanity-checking; it'll - // go away pretty soon. XXX - // Without UseCompressedOops, this is simply: - // oop->length() * HeapWordsPerOop; - // With narrowOops, HeapWordsPerOop is 1/2 or equal 0 as an integer. - // The oop elements are aligned up to wordSize - const uint HeapWordsPerOop = heapOopSize/HeapWordSize; - uint old_res; - if (HeapWordsPerOop > 0) { - old_res = length * HeapWordsPerOop; - } else { - old_res = align_up((uint)length, OopsPerHeapWord)/OopsPerHeapWord; - } - assert(res == old_res, "Inconsistency between old and new."); -#endif // ASSERT - return res; - } - public: // Returns the offset of the first element. static int base_offset_in_bytes() { @@ -94,16 +68,15 @@ class objArrayOopDesc : public arrayOopDesc { oop replace_if_null(int index, oop exchange_value); // Sizing - static int header_size() { return arrayOopDesc::header_size(T_OBJECT); } size_t object_size() { return object_size(length()); } static size_t object_size(int length) { // This returns the object size in HeapWords. - uint asz = array_size(length); - uint osz = align_object_size(header_size() + asz); - assert(osz >= asz, "no overflow"); - assert((int)osz > 0, "no overflow"); - return (size_t)osz; + size_t asz = (size_t)length * heapOopSize; + size_t size_words = heap_word_size(base_offset_in_bytes() + asz); + size_t osz = align_object_size(size_words); + assert(osz < max_jint, "no overflow"); + return osz; } Klass* element_klass(); diff --git a/src/hotspot/share/opto/runtime.cpp b/src/hotspot/share/opto/runtime.cpp index c5f22e8ebbb84..65e99edba9706 100644 --- a/src/hotspot/share/opto/runtime.cpp +++ b/src/hotspot/share/opto/runtime.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1998, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -319,14 +319,17 @@ JRT_BLOCK_ENTRY(void, OptoRuntime::new_array_nozero_C(Klass* array_type, int len // Zero array here if the caller is deoptimized. const size_t size = TypeArrayKlass::cast(array_type)->oop_size(result); BasicType elem_type = TypeArrayKlass::cast(array_type)->element_type(); - const size_t hs = arrayOopDesc::header_size(elem_type); - // Align to next 8 bytes to avoid trashing arrays's length. - const size_t aligned_hs = align_object_offset(hs); + size_t hs_bytes = arrayOopDesc::base_offset_in_bytes(elem_type); + assert(is_aligned(hs_bytes, BytesPerInt), "must be 4 byte aligned"); HeapWord* obj = cast_from_oop(result); - if (aligned_hs > hs) { - Copy::zero_to_words(obj+hs, aligned_hs-hs); + if (!is_aligned(hs_bytes, BytesPerLong)) { + *reinterpret_cast(reinterpret_cast(obj) + hs_bytes) = 0; + hs_bytes += BytesPerInt; } + // Optimized zeroing. + assert(is_aligned(hs_bytes, BytesPerLong), "must be 8-byte aligned"); + const size_t aligned_hs = hs_bytes / BytesPerLong; Copy::fill_to_aligned_words(obj+aligned_hs, size-aligned_hs); } diff --git a/src/hotspot/share/opto/type.cpp b/src/hotspot/share/opto/type.cpp index 84d092f2ffd59..b042e79309e23 100644 --- a/src/hotspot/share/opto/type.cpp +++ b/src/hotspot/share/opto/type.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -5169,18 +5169,17 @@ void TypeAryPtr::dump2( Dict &d, uint depth, outputStream *st ) const { } if( _offset != 0 ) { - int header_size = objArrayOopDesc::header_size() * wordSize; + BasicType basic_elem_type = elem()->basic_type(); + int header_size = arrayOopDesc::base_offset_in_bytes(basic_elem_type); if( _offset == OffsetTop ) st->print("+undefined"); else if( _offset == OffsetBot ) st->print("+any"); else if( _offset < header_size ) st->print("+%d", _offset); else { - BasicType basic_elem_type = elem()->basic_type(); if (basic_elem_type == T_ILLEGAL) { st->print("+any"); } else { - int array_base = arrayOopDesc::base_offset_in_bytes(basic_elem_type); int elem_size = type2aelembytes(basic_elem_type); - st->print("[%d]", (_offset - array_base)/elem_size); + st->print("[%d]", (_offset - header_size)/elem_size); } } } diff --git a/src/hotspot/share/prims/unsafe.cpp b/src/hotspot/share/prims/unsafe.cpp index 1a05c0fa13e61..9540c98f6e014 100644 --- a/src/hotspot/share/prims/unsafe.cpp +++ b/src/hotspot/share/prims/unsafe.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -67,7 +67,7 @@ #define MAX_OBJECT_SIZE \ - ( arrayOopDesc::header_size(T_DOUBLE) * HeapWordSize \ + ( arrayOopDesc::base_offset_in_bytes(T_DOUBLE) \ + ((julong)max_jint * sizeof(double)) ) #define UNSAFE_ENTRY(result_type, header) \ diff --git a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/memory/Universe.java b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/memory/Universe.java index f2f5422ad0490..c4ab8e32c0bb0 100644 --- a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/memory/Universe.java +++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/memory/Universe.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -116,13 +116,6 @@ public void printOn(PrintStream tty) { heap().printOn(tty); } - // Check whether an element of a typeArrayOop with the given type must be - // aligned 0 mod 8. The typeArrayOop itself must be aligned at least this - // strongly. - public static boolean elementTypeShouldBeAligned(BasicType type) { - return type == BasicType.T_DOUBLE || type == BasicType.T_LONG; - } - // Check whether an object field (static/non-static) of the given type must be // aligned 0 mod 8. public static boolean fieldTypeShouldBeAligned(BasicType type) { diff --git a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/Array.java b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/Array.java index 6ba23c9ea40f8..3f96f72cf0408 100644 --- a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/Array.java +++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/Array.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -57,28 +57,27 @@ private static void initialize(TypeDataBase db) throws WrongTypeException { private static long lengthOffsetInBytes=0; private static long typeSize; + // Check whether an element of a arrayOop with the given type must be + // aligned 0 mod 8. The arrayOop itself must be aligned at least this + // strongly. + private static boolean elementTypeShouldBeAligned(BasicType type) { + if (VM.getVM().isLP64()) { + if (type == BasicType.T_OBJECT || type == BasicType.T_ARRAY) { + return !VM.getVM().isCompressedOopsEnabled(); + } + } + return type == BasicType.T_DOUBLE || type == BasicType.T_LONG; + } + private static long headerSizeInBytes() { if (headerSize != 0) { return headerSize; } - if (VM.getVM().isCompressedKlassPointersEnabled()) { - headerSize = typeSize; - } else { - headerSize = VM.getVM().alignUp(typeSize + VM.getVM().getIntSize(), - VM.getVM().getHeapWordSize()); - } + headerSize = lengthOffsetInBytes() + VM.getVM().getIntSize(); return headerSize; } - private static long headerSize(BasicType type) { - if (Universe.elementTypeShouldBeAligned(type)) { - return alignObjectSize(headerSizeInBytes())/VM.getVM().getHeapWordSize(); - } else { - return headerSizeInBytes()/VM.getVM().getHeapWordSize(); - } - } - - private long lengthOffsetInBytes() { + private static long lengthOffsetInBytes() { if (lengthOffsetInBytes != 0) { return lengthOffsetInBytes; } @@ -108,7 +107,13 @@ public long getObjectSize() { } public static long baseOffsetInBytes(BasicType type) { - return headerSize(type) * VM.getVM().getHeapWordSize(); + long typeSizeInBytes = headerSizeInBytes(); + if (elementTypeShouldBeAligned(type)) { + VM vm = VM.getVM(); + return vm.alignUp(typeSizeInBytes, vm.getVM().getHeapWordSize()); + } else { + return typeSizeInBytes; + } } public boolean isArray() { return true; } diff --git a/test/hotspot/gtest/oops/test_arrayOop.cpp b/test/hotspot/gtest/oops/test_arrayOop.cpp index 84063813be339..e67e6e6c13b92 100644 --- a/test/hotspot/gtest/oops/test_arrayOop.cpp +++ b/test/hotspot/gtest/oops/test_arrayOop.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2016, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -27,19 +27,11 @@ #include "unittest.hpp" #include "utilities/globalDefinitions.hpp" -class arrayOopDescTest { - public: - - static int header_size_in_bytes() { - return arrayOopDesc::header_size_in_bytes(); - } -}; - static bool check_max_length_overflow(BasicType type) { julong length = arrayOopDesc::max_array_length(type); julong bytes_per_element = type2aelembytes(type); julong bytes = length * bytes_per_element - + arrayOopDescTest::header_size_in_bytes(); + + arrayOopDesc::base_offset_in_bytes(type); return (julong) (size_t) bytes == bytes; } @@ -87,3 +79,47 @@ TEST_VM(arrayOopDesc, narrowOop) { ASSERT_PRED1(check_max_length_overflow, T_NARROWOOP); } // T_VOID and T_ADDRESS are not supported by max_array_length() + +TEST_VM(arrayOopDesc, base_offset) { +#ifdef _LP64 + if (UseCompressedClassPointers) { + EXPECT_EQ(arrayOopDesc::base_offset_in_bytes(T_BOOLEAN), 16); + EXPECT_EQ(arrayOopDesc::base_offset_in_bytes(T_BYTE), 16); + EXPECT_EQ(arrayOopDesc::base_offset_in_bytes(T_SHORT), 16); + EXPECT_EQ(arrayOopDesc::base_offset_in_bytes(T_CHAR), 16); + EXPECT_EQ(arrayOopDesc::base_offset_in_bytes(T_INT), 16); + EXPECT_EQ(arrayOopDesc::base_offset_in_bytes(T_FLOAT), 16); + EXPECT_EQ(arrayOopDesc::base_offset_in_bytes(T_LONG), 16); + EXPECT_EQ(arrayOopDesc::base_offset_in_bytes(T_DOUBLE), 16); + EXPECT_EQ(arrayOopDesc::base_offset_in_bytes(T_OBJECT), 16); + EXPECT_EQ(arrayOopDesc::base_offset_in_bytes(T_ARRAY), 16); + } else { + EXPECT_EQ(arrayOopDesc::base_offset_in_bytes(T_BOOLEAN), 20); + EXPECT_EQ(arrayOopDesc::base_offset_in_bytes(T_BYTE), 20); + EXPECT_EQ(arrayOopDesc::base_offset_in_bytes(T_SHORT), 20); + EXPECT_EQ(arrayOopDesc::base_offset_in_bytes(T_CHAR), 20); + EXPECT_EQ(arrayOopDesc::base_offset_in_bytes(T_INT), 20); + EXPECT_EQ(arrayOopDesc::base_offset_in_bytes(T_FLOAT), 20); + EXPECT_EQ(arrayOopDesc::base_offset_in_bytes(T_LONG), 24); + EXPECT_EQ(arrayOopDesc::base_offset_in_bytes(T_DOUBLE), 24); + if (UseCompressedOops) { + EXPECT_EQ(arrayOopDesc::base_offset_in_bytes(T_OBJECT), 20); + EXPECT_EQ(arrayOopDesc::base_offset_in_bytes(T_ARRAY), 20); + } else { + EXPECT_EQ(arrayOopDesc::base_offset_in_bytes(T_OBJECT), 24); + EXPECT_EQ(arrayOopDesc::base_offset_in_bytes(T_ARRAY), 24); + } + } +#else + EXPECT_EQ(arrayOopDesc::base_offset_in_bytes(T_BOOLEAN), 12); + EXPECT_EQ(arrayOopDesc::base_offset_in_bytes(T_BYTE), 12); + EXPECT_EQ(arrayOopDesc::base_offset_in_bytes(T_SHORT), 12); + EXPECT_EQ(arrayOopDesc::base_offset_in_bytes(T_CHAR), 12); + EXPECT_EQ(arrayOopDesc::base_offset_in_bytes(T_INT), 12); + EXPECT_EQ(arrayOopDesc::base_offset_in_bytes(T_FLOAT), 12); + EXPECT_EQ(arrayOopDesc::base_offset_in_bytes(T_LONG), 16); + EXPECT_EQ(arrayOopDesc::base_offset_in_bytes(T_DOUBLE), 16); + EXPECT_EQ(arrayOopDesc::base_offset_in_bytes(T_OBJECT), 12); + EXPECT_EQ(arrayOopDesc::base_offset_in_bytes(T_ARRAY), 12); +#endif +} diff --git a/test/hotspot/gtest/oops/test_objArrayOop.cpp b/test/hotspot/gtest/oops/test_objArrayOop.cpp new file mode 100644 index 0000000000000..60cf6242dd596 --- /dev/null +++ b/test/hotspot/gtest/oops/test_objArrayOop.cpp @@ -0,0 +1,57 @@ +/* + * Copyright Amazon.com Inc. or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#include "precompiled.hpp" +#include "oops/objArrayOop.hpp" +#include "unittest.hpp" +#include "utilities/globalDefinitions.hpp" + +TEST_VM(objArrayOop, osize) { + static const struct { + int objal; bool ccp; bool coops; int result; + } x[] = { +// ObjAligInB, UseCCP, UseCoops, object size in heap words +#ifdef _LP64 + { 8, false, false, 4 }, // 20 byte header, 8 byte oops + { 8, false, true, 3 }, // 20 byte header, 4 byte oops + { 8, true, false, 3 }, // 16 byte header, 8 byte oops + { 8, true, true, 3 }, // 16 byte header, 4 byte oops + { 16, false, false, 4 }, // 20 byte header, 8 byte oops, 16-byte align + { 16, false, true, 4 }, // 20 byte header, 4 byte oops, 16-byte align + { 16, true, false, 4 }, // 16 byte header, 8 byte oops, 16-byte align + { 16, true, true, 4 }, // 16 byte header, 4 byte oops, 16-byte align + { 256, false, false, 32 }, // 20 byte header, 8 byte oops, 256-byte align + { 256, false, true, 32 }, // 20 byte header, 4 byte oops, 256-byte align + { 256, true, false, 32 }, // 16 byte header, 8 byte oops, 256-byte align + { 256, true, true, 32 }, // 16 byte header, 4 byte oops, 256-byte align +#else + { 8, false, false, 4 }, // 12 byte header, 4 byte oops, wordsize 4 +#endif + { -1, false, false, -1 } + }; + for (int i = 0; x[i].result != -1; i++) { + if (x[i].objal == (int)ObjectAlignmentInBytes && x[i].ccp == UseCompressedClassPointers && x[i].coops == UseCompressedOops) { + EXPECT_EQ(objArrayOopDesc::object_size(1), (size_t)x[i].result); + } + } +} diff --git a/test/hotspot/jtreg/gtest/ArrayTests.java b/test/hotspot/jtreg/gtest/ArrayTests.java new file mode 100644 index 0000000000000..b1afa4795d22b --- /dev/null +++ b/test/hotspot/jtreg/gtest/ArrayTests.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. + * + */ + +/* + * This tests object array sizes by running gtests with different settings. + */ + +/* @test id=with-coops-with-ccp + * @summary Run object array size tests with compressed oops and compressed class pointers + * @library /test/lib + * @modules java.base/jdk.internal.misc + * java.xml + * @run main/native GTestWrapper --gtest_filter=arrayOop -XX:+UseCompressedClassPointers -XX:+UseCompressedOops + */ +/* @test id=with-coops-no-ccp + * @summary Run object array size tests with compressed oops and compressed class pointers + * @library /test/lib + * @modules java.base/jdk.internal.misc + * java.xml + * @run main/native GTestWrapper --gtest_filter=arrayOop -XX:-UseCompressedClassPointers -XX:+UseCompressedOops + */ +/* @test id=no-coops-with-ccp + * @summary Run object array size tests with compressed oops and compressed class pointers + * @library /test/lib + * @modules java.base/jdk.internal.misc + * java.xml + * @run main/native GTestWrapper --gtest_filter=arrayOop -XX:+UseCompressedClassPointers -XX:-UseCompressedOops + */ +/* @test id=no-coops-no-ccp + * @summary Run object array size tests with compressed oops and compressed class pointers + * @library /test/lib + * @modules java.base/jdk.internal.misc + * java.xml + * @run main/native GTestWrapper --gtest_filter=arrayOop -XX:-UseCompressedClassPointers -XX:-UseCompressedOops + */ diff --git a/test/hotspot/jtreg/gtest/ObjArrayTests.java b/test/hotspot/jtreg/gtest/ObjArrayTests.java new file mode 100644 index 0000000000000..baae1840417ef --- /dev/null +++ b/test/hotspot/jtreg/gtest/ObjArrayTests.java @@ -0,0 +1,85 @@ +/* + * 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. + * + */ + +/* + * This tests object array sizes by running gtests with different settings. + */ + +/* @test id=with-coops-with-ccp + * @summary Run object array size tests with compressed oops and compressed class pointers + * @library /test/lib + * @modules java.base/jdk.internal.misc + * java.xml + * @run main/native GTestWrapper --gtest_filter=objArrayOop -XX:+UseCompressedClassPointers -XX:+UseCompressedOops + */ +/* @test id=with-coops-no-ccp + * @summary Run object array size tests with compressed oops and compressed class pointers + * @library /test/lib + * @modules java.base/jdk.internal.misc + * java.xml + * @run main/native GTestWrapper --gtest_filter=objArrayOop -XX:-UseCompressedClassPointers -XX:+UseCompressedOops + */ +/* @test id=no-coops-with-ccp + * @summary Run object array size tests with compressed oops and compressed class pointers + * @library /test/lib + * @modules java.base/jdk.internal.misc + * java.xml + * @run main/native GTestWrapper --gtest_filter=objArrayOop -XX:+UseCompressedClassPointers -XX:-UseCompressedOops + */ +/* @test id=no-coops-no-ccp + * @summary Run object array size tests with compressed oops and compressed class pointers + * @library /test/lib + * @modules java.base/jdk.internal.misc + * java.xml + * @run main/native GTestWrapper --gtest_filter=objArrayOop -XX:-UseCompressedClassPointers -XX:-UseCompressedOops + */ + +/* @test id=with-coops-with-ccp-large-align + * @summary Run object array size tests with compressed oops and compressed class pointers + * @library /test/lib + * @modules java.base/jdk.internal.misc + * java.xml + * @run main/native GTestWrapper --gtest_filter=objArrayOop -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:ObjAlignmentInBytes=256 + */ +/* @test id=with-coops-no-ccp-large-align + * @summary Run object array size tests with compressed oops and compressed class pointers + * @library /test/lib + * @modules java.base/jdk.internal.misc + * java.xml + * @run main/native GTestWrapper --gtest_filter=objArrayOop -XX:-UseCompressedClassPointers -XX:+UseCompressedOops -XX:ObjAlignmentInBytes=256 + */ +/* @test id=no-coops-with-ccp-large-align + * @summary Run object array size tests with compressed oops and compressed class pointers + * @library /test/lib + * @modules java.base/jdk.internal.misc + * java.xml + * @run main/native GTestWrapper --gtest_filter=objArrayOop -XX:+UseCompressedClassPointers -XX:-UseCompressedOops -XX:ObjAlignmentInBytes=256 + */ +/* @test id=no-coops-no-ccp-large-align + * @summary Run object array size tests with compressed oops and compressed class pointers + * @library /test/lib + * @modules java.base/jdk.internal.misc + * java.xml + * @run main/native GTestWrapper --gtest_filter=objArrayOop -XX:-UseCompressedClassPointers -XX:-UseCompressedOops -XX:ObjAlignmentInBytes=256 + */ diff --git a/test/hotspot/jtreg/runtime/FieldLayout/ArrayBaseOffsets.java b/test/hotspot/jtreg/runtime/FieldLayout/ArrayBaseOffsets.java new file mode 100644 index 0000000000000..b679e866ac82a --- /dev/null +++ b/test/hotspot/jtreg/runtime/FieldLayout/ArrayBaseOffsets.java @@ -0,0 +1,113 @@ +/* + * Copyright Amazon.com Inc. or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test id=with-coops-no-ccp + * @library /test/lib + * @requires vm.bits == "64" + * @modules java.base/jdk.internal.misc + * @run main/othervm -XX:+UseCompressedOops -XX:-UseCompressedClassPointers ArrayBaseOffsets + */ +/* + * @test id=with-coops-with-ccp + * @library /test/lib + * @requires vm.bits == "64" + * @requires vm.opt.UseCompressedClassPointers != false + * @modules java.base/jdk.internal.misc + * @run main/othervm -XX:+UseCompressedOops -XX:+UseCompressedClassPointers ArrayBaseOffsets + */ +/* + * @test id=no-coops-no-ccp + * @library /test/lib + * @requires vm.bits == "64" + * @modules java.base/jdk.internal.misc + * @run main/othervm -XX:-UseCompressedOops -XX:-UseCompressedClassPointers ArrayBaseOffsets + */ +/* + * @test id=no-coops-with-ccp + * @library /test/lib + * @requires vm.bits == "64" + * @requires vm.opt.UseCompressedClassPointers != false + * @modules java.base/jdk.internal.misc + * @run main/othervm -XX:-UseCompressedOops -XX:+UseCompressedClassPointers ArrayBaseOffsets + */ +/* + * @test id=32bit + * @library /test/lib + * @requires vm.bits == "32" + * @modules java.base/jdk.internal.misc + * @run main/othervm ArrayBaseOffsets + */ + +import jdk.internal.misc.Unsafe; + +import java.lang.management.ManagementFactory; +import java.lang.management.RuntimeMXBean; +import java.util.List; + +import jdk.test.lib.Asserts; +import jdk.test.lib.Platform; + +public class ArrayBaseOffsets { + + private static final boolean COOP; + private static final boolean CCP; + + static { + if (Platform.is64bit()) { + RuntimeMXBean runtime = ManagementFactory.getRuntimeMXBean(); + List vmargs = runtime.getInputArguments(); + CCP = !vmargs.contains("-XX:-UseCompressedClassPointers"); + COOP = System.getProperty("java.vm.compressedOopsMode") != null; + } else { + COOP = CCP = false; + } + } + + static public void main(String[] args) { + Unsafe unsafe = Unsafe.getUnsafe(); + int intOffset, longOffset; + if (Platform.is64bit()) { + if (CCP) { + intOffset = 16; + longOffset = 16; + } else { + intOffset = 20; + longOffset = 24; + } + } else { + intOffset = 12; + longOffset = 16; + } + Asserts.assertEquals(unsafe.arrayBaseOffset(boolean[].class), intOffset, "Misplaced boolean array base"); + Asserts.assertEquals(unsafe.arrayBaseOffset(byte[].class), intOffset, "Misplaced byte array base"); + Asserts.assertEquals(unsafe.arrayBaseOffset(char[].class), intOffset, "Misplaced char array base"); + Asserts.assertEquals(unsafe.arrayBaseOffset(short[].class), intOffset, "Misplaced short array base"); + Asserts.assertEquals(unsafe.arrayBaseOffset(int[].class), intOffset, "Misplaced int array base"); + Asserts.assertEquals(unsafe.arrayBaseOffset(long[].class), longOffset, "Misplaced long array base"); + Asserts.assertEquals(unsafe.arrayBaseOffset(float[].class), intOffset, "Misplaced float array base"); + Asserts.assertEquals(unsafe.arrayBaseOffset(double[].class), longOffset, "Misplaced double array base"); + int expectedObjArrayOffset = (COOP || !Platform.is64bit()) ? intOffset : longOffset; + Asserts.assertEquals(unsafe.arrayBaseOffset(Object[].class), expectedObjArrayOffset, "Misplaced object array base"); + } +} diff --git a/test/jdk/java/lang/instrument/GetObjectSizeIntrinsicsTest.java b/test/jdk/java/lang/instrument/GetObjectSizeIntrinsicsTest.java index fc3b1a66d2a5a..22c5069f3e78e 100644 --- a/test/jdk/java/lang/instrument/GetObjectSizeIntrinsicsTest.java +++ b/test/jdk/java/lang/instrument/GetObjectSizeIntrinsicsTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2022, Red Hat, Inc. All rights reserved. + * Copyright (c) 2020, 2024, Red Hat, Inc. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -313,6 +313,9 @@ public class GetObjectSizeIntrinsicsTest extends ASimpleInstrumentationTestCase static final int LARGE_INT_ARRAY_SIZE = 1024*1024*1024 + 1024; static final int LARGE_OBJ_ARRAY_SIZE = (4096/(int)REF_SIZE)*1024*1024 + 1024; + static final boolean CCP = WhiteBox.getWhiteBox().getBooleanVMFlag("UseCompressedClassPointers"); + static final int ARRAY_HEADER_SIZE = CCP ? 16 : (Platform.is64bit() ? 20 : 16); + final String mode; public GetObjectSizeIntrinsicsTest(String name, String mode) { @@ -396,7 +399,7 @@ private void testSize_fieldObject() { } private void testSize_newSmallIntArray() { - long expected = roundUp(4L*SMALL_ARRAY_SIZE + 16, OBJ_ALIGN); + long expected = roundUp(4L*SMALL_ARRAY_SIZE + ARRAY_HEADER_SIZE, OBJ_ALIGN); for (int c = 0; c < ITERS; c++) { assertEquals(expected, fInst.getObjectSize(new int[SMALL_ARRAY_SIZE])); } @@ -404,7 +407,7 @@ private void testSize_newSmallIntArray() { private void testSize_localSmallIntArray() { int[] arr = new int[SMALL_ARRAY_SIZE]; - long expected = roundUp(4L*SMALL_ARRAY_SIZE + 16, OBJ_ALIGN); + long expected = roundUp(4L*SMALL_ARRAY_SIZE + ARRAY_HEADER_SIZE, OBJ_ALIGN); for (int c = 0; c < ITERS; c++) { assertEquals(expected, fInst.getObjectSize(arr)); } @@ -413,14 +416,14 @@ private void testSize_localSmallIntArray() { static int[] smallArr = new int[SMALL_ARRAY_SIZE]; private void testSize_fieldSmallIntArray() { - long expected = roundUp(4L*SMALL_ARRAY_SIZE + 16, OBJ_ALIGN); + long expected = roundUp(4L*SMALL_ARRAY_SIZE + ARRAY_HEADER_SIZE, OBJ_ALIGN); for (int c = 0; c < ITERS; c++) { assertEquals(expected, fInst.getObjectSize(smallArr)); } } private void testSize_newSmallObjArray() { - long expected = roundUp(REF_SIZE*SMALL_ARRAY_SIZE + 16, OBJ_ALIGN); + long expected = roundUp(REF_SIZE*SMALL_ARRAY_SIZE + ARRAY_HEADER_SIZE, OBJ_ALIGN); for (int c = 0; c < ITERS; c++) { assertEquals(expected, fInst.getObjectSize(new Object[SMALL_ARRAY_SIZE])); } @@ -428,7 +431,7 @@ private void testSize_newSmallObjArray() { private void testSize_localSmallObjArray() { Object[] arr = new Object[SMALL_ARRAY_SIZE]; - long expected = roundUp(REF_SIZE*SMALL_ARRAY_SIZE + 16, OBJ_ALIGN); + long expected = roundUp(REF_SIZE*SMALL_ARRAY_SIZE + ARRAY_HEADER_SIZE, OBJ_ALIGN); for (int c = 0; c < ITERS; c++) { assertEquals(expected, fInst.getObjectSize(arr)); } @@ -437,7 +440,7 @@ private void testSize_localSmallObjArray() { static Object[] smallObjArr = new Object[SMALL_ARRAY_SIZE]; private void testSize_fieldSmallObjArray() { - long expected = roundUp(REF_SIZE*SMALL_ARRAY_SIZE + 16, OBJ_ALIGN); + long expected = roundUp(REF_SIZE*SMALL_ARRAY_SIZE + ARRAY_HEADER_SIZE, OBJ_ALIGN); for (int c = 0; c < ITERS; c++) { assertEquals(expected, fInst.getObjectSize(smallObjArr)); } @@ -445,7 +448,7 @@ private void testSize_fieldSmallObjArray() { private void testSize_localLargeIntArray() { int[] arr = new int[LARGE_INT_ARRAY_SIZE]; - long expected = roundUp(4L*LARGE_INT_ARRAY_SIZE + 16, OBJ_ALIGN); + long expected = roundUp(4L*LARGE_INT_ARRAY_SIZE + ARRAY_HEADER_SIZE, OBJ_ALIGN); for (int c = 0; c < ITERS; c++) { assertEquals(expected, fInst.getObjectSize(arr)); } @@ -453,7 +456,7 @@ private void testSize_localLargeIntArray() { private void testSize_localLargeObjArray() { Object[] arr = new Object[LARGE_OBJ_ARRAY_SIZE]; - long expected = roundUp(REF_SIZE*LARGE_OBJ_ARRAY_SIZE + 16, OBJ_ALIGN); + long expected = roundUp(REF_SIZE*LARGE_OBJ_ARRAY_SIZE + ARRAY_HEADER_SIZE, OBJ_ALIGN); for (int c = 0; c < ITERS; c++) { assertEquals(expected, fInst.getObjectSize(arr)); } From 93a2e773a508ef7960214e20c2df5e8456f2e8c2 Mon Sep 17 00:00:00 2001 From: Jan Lahoda Date: Fri, 23 Feb 2024 10:08:56 +0000 Subject: [PATCH 29/38] 8326129: Java Record Pattern Match leads to infinite loop Reviewed-by: vromero --- .../sun/tools/javac/comp/TransPatterns.java | 1 + .../tools/javac/patterns/TranslationTest.java | 85 ++++++++++++++++++- 2 files changed, 84 insertions(+), 2 deletions(-) diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/TransPatterns.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/TransPatterns.java index 089d9236dec91..5628fc119cc37 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/TransPatterns.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/TransPatterns.java @@ -988,6 +988,7 @@ public void resolve(VarSymbol commonBinding, commonBinding.type.tsym == currentBinding.type.tsym && commonBinding.isUnnamedVariable() == currentBinding.isUnnamedVariable() && !previousNullable && + !currentNullable && new TreeDiffer(List.of(commonBinding), List.of(currentBinding)) .scan(commonNestedExpression, currentNestedExpression)) { accummulator.add(c.head); diff --git a/test/langtools/tools/javac/patterns/TranslationTest.java b/test/langtools/tools/javac/patterns/TranslationTest.java index b509b2be3f92a..f69783f6b85f8 100644 --- a/test/langtools/tools/javac/patterns/TranslationTest.java +++ b/test/langtools/tools/javac/patterns/TranslationTest.java @@ -23,7 +23,7 @@ /** * @test - * @bug 8291769 + * @bug 8291769 8326129 * @summary Check expected translation of various pattern related constructs * @library /tools/lib * @modules jdk.compiler/com.sun.tools.javac.api @@ -31,7 +31,7 @@ * jdk.compiler/com.sun.tools.javac.main * jdk.compiler/com.sun.tools.javac.tree * jdk.compiler/com.sun.tools.javac.util - * @build toolbox.ToolBox toolbox.JavacTask + * @build toolbox.ToolBox toolbox.JavacTask toolbox.JavaTask * @run main TranslationTest */ @@ -48,6 +48,7 @@ import com.sun.tools.javac.tree.TreeScanner; import com.sun.tools.javac.util.Context; import com.sun.tools.javac.util.Context.Factory; +import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; @@ -56,6 +57,7 @@ import java.util.List; import toolbox.TestRunner; +import toolbox.JavaTask; import toolbox.JavacTask; import toolbox.Task; import toolbox.ToolBox; @@ -188,6 +190,38 @@ private int test(Object obj) { """); } + @Test //JDK-8326129 + public void testRunWithNull(Path base) throws Exception { + doRunTest(base, + new String[]{""" + package lib; + public record Box(Object o) {} + """}, + """ + import lib.*; + public class Test { + public static void main(String... args) { + System.err.println(new Test().test(new Box(null))); + } + private int test(Box b) { + return switch (b) { + case Box(Integer i) -> 0; + case Box(Object o) when check(o) -> 1; + case Box(Object o) -> 2; + }; + } + private static int c; + private boolean check(Object o) { + System.err.println("check: " + o); + if (c++ > 10) throw new IllegalStateException(); + return o != null; + } + } + """, + "check: null", + "2"); + } + private void doTest(Path base, String[] libraryCode, String testCode, Callback callback, String expectedOutput) throws IOException { Path current = base.resolve("."); @@ -322,4 +356,51 @@ public JCTree translateTopLevelClass(Env env, JCTree cdef, TreeMake } } + + private void doRunTest(Path base, String[] libraryCode, String testCode, + String... expectedOutput) throws IOException { + Path current = base.resolve("."); + Path libClasses = current.resolve("libClasses"); + + Files.createDirectories(libClasses); + + if (libraryCode.length != 0) { + Path libSrc = current.resolve("lib-src"); + + for (String code : libraryCode) { + tb.writeJavaFiles(libSrc, code); + } + + new JavacTask(tb) + .outdir(libClasses) + .files(tb.findJavaFiles(libSrc)) + .run(); + } + + Path src = current.resolve("src"); + tb.writeJavaFiles(src, testCode); + + Path classes = current.resolve("classes"); + + Files.createDirectories(classes); + + new JavacTask(tb) + .options("-Xlint:-preview", + "--class-path", libClasses.toString()) + .outdir(classes) + .files(tb.findJavaFiles(src)) + .run() + .writeAll(); + + List log = new JavaTask(tb) + .classpath(libClasses.toString() + File.pathSeparatorChar + classes.toString()) + .classArgs("Test") + .run() + .getOutputLines(Task.OutputKind.STDERR); + + if (!List.of(expectedOutput).equals(log)) { + throw new AssertionError("Expected: " + expectedOutput + + "but got: " + log); + } + } } From 5d414da50459b7a1e6f0f537ff3b318854b2c427 Mon Sep 17 00:00:00 2001 From: Roland Westrelin Date: Fri, 23 Feb 2024 10:09:06 +0000 Subject: [PATCH 30/38] 8325372: Shenandoah: SIGSEGV crash in unnecessary_acquire due to LoadStore split through phi Reviewed-by: shade, rkennke, thartmann --- .../gc/shenandoah/c2/shenandoahSupport.cpp | 17 +++- .../gc/shenandoah/c2/shenandoahSupport.hpp | 1 + src/hotspot/share/opto/memnode.cpp | 1 + ...tUnsafeLoadStoreMergedHeapStableTests.java | 82 +++++++++++++++++++ 4 files changed, 100 insertions(+), 1 deletion(-) create mode 100644 test/hotspot/jtreg/gc/shenandoah/compiler/TestUnsafeLoadStoreMergedHeapStableTests.java diff --git a/src/hotspot/share/gc/shenandoah/c2/shenandoahSupport.cpp b/src/hotspot/share/gc/shenandoah/c2/shenandoahSupport.cpp index c9c566ad611b6..14fb038a6c6a8 100644 --- a/src/hotspot/share/gc/shenandoah/c2/shenandoahSupport.cpp +++ b/src/hotspot/share/gc/shenandoah/c2/shenandoahSupport.cpp @@ -1734,11 +1734,26 @@ bool ShenandoahBarrierC2Support::identical_backtoback_ifs(Node* n, PhaseIdealLoo return true; } +bool ShenandoahBarrierC2Support::merge_point_safe(Node* region) { + for (DUIterator_Fast imax, i = region->fast_outs(imax); i < imax; i++) { + Node* n = region->fast_out(i); + if (n->is_LoadStore()) { + // Splitting a LoadStore node through phi, causes it to lose its SCMemProj: the split if code doesn't have support + // for a LoadStore at the region the if is split through because that's not expected to happen (LoadStore nodes + // should be between barrier nodes). It does however happen with Shenandoah though because barriers can get + // expanded around a LoadStore node. + return false; + } + } + return true; +} + + void ShenandoahBarrierC2Support::merge_back_to_back_tests(Node* n, PhaseIdealLoop* phase) { assert(is_heap_stable_test(n), "no other tests"); if (identical_backtoback_ifs(n, phase)) { Node* n_ctrl = n->in(0); - if (phase->can_split_if(n_ctrl)) { + if (phase->can_split_if(n_ctrl) && merge_point_safe(n_ctrl)) { IfNode* dom_if = phase->idom(n_ctrl)->as_If(); if (is_heap_stable_test(n)) { Node* gc_state_load = n->in(1)->in(1)->in(1)->in(1); diff --git a/src/hotspot/share/gc/shenandoah/c2/shenandoahSupport.hpp b/src/hotspot/share/gc/shenandoah/c2/shenandoahSupport.hpp index 032f338aa8895..7a6ed74f56393 100644 --- a/src/hotspot/share/gc/shenandoah/c2/shenandoahSupport.hpp +++ b/src/hotspot/share/gc/shenandoah/c2/shenandoahSupport.hpp @@ -65,6 +65,7 @@ class ShenandoahBarrierC2Support : public AllStatic { static void test_in_cset(Node*& ctrl, Node*& not_cset_ctrl, Node* val, Node* raw_mem, PhaseIdealLoop* phase); static void move_gc_state_test_out_of_loop(IfNode* iff, PhaseIdealLoop* phase); static void merge_back_to_back_tests(Node* n, PhaseIdealLoop* phase); + static bool merge_point_safe(Node* region); static bool identical_backtoback_ifs(Node *n, PhaseIdealLoop* phase); static void fix_ctrl(Node* barrier, Node* region, const MemoryGraphFixer& fixer, Unique_Node_List& uses, Unique_Node_List& uses_to_ignore, uint last, PhaseIdealLoop* phase); static IfNode* find_unswitching_candidate(const IdealLoopTree *loop, PhaseIdealLoop* phase); diff --git a/src/hotspot/share/opto/memnode.cpp b/src/hotspot/share/opto/memnode.cpp index 010a13a07bac9..e0a364d5056b3 100644 --- a/src/hotspot/share/opto/memnode.cpp +++ b/src/hotspot/share/opto/memnode.cpp @@ -3408,6 +3408,7 @@ Node *MemBarNode::Ideal(PhaseGVN *phase, bool can_reshape) { my_mem = load_node; } else { assert(my_mem->unique_out() == this, "sanity"); + assert(!trailing_load_store(), "load store node can't be eliminated"); del_req(Precedent); phase->is_IterGVN()->_worklist.push(my_mem); // remove dead node later my_mem = nullptr; diff --git a/test/hotspot/jtreg/gc/shenandoah/compiler/TestUnsafeLoadStoreMergedHeapStableTests.java b/test/hotspot/jtreg/gc/shenandoah/compiler/TestUnsafeLoadStoreMergedHeapStableTests.java new file mode 100644 index 0000000000000..e7f9c777ef8af --- /dev/null +++ b/test/hotspot/jtreg/gc/shenandoah/compiler/TestUnsafeLoadStoreMergedHeapStableTests.java @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2024, Red Hat, Inc. All rights reserved. + * 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 8325372 + * @summary fusion of heap stable test causes GetAndSet node to be removed + * @requires vm.gc.Shenandoah + * @modules java.base/jdk.internal.misc:+open + * + * @run main/othervm -XX:+UseShenandoahGC -XX:-BackgroundCompilation TestUnsafeLoadStoreMergedHeapStableTests + */ + +import jdk.internal.misc.Unsafe; + +import java.lang.reflect.Field; + +public class TestUnsafeLoadStoreMergedHeapStableTests { + + static final jdk.internal.misc.Unsafe UNSAFE = Unsafe.getUnsafe(); + static long F_OFFSET; + + static class A { + Object f; + } + + static { + try { + Field fField = A.class.getDeclaredField("f"); + F_OFFSET = UNSAFE.objectFieldOffset(fField); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + static Object testHelper(boolean flag, Object o, long offset, Object x) { + if (flag) { + return UNSAFE.getAndSetObject(o, offset, x); + } + return null; + } + + static Object field; + + + static Object test1(boolean flag, Object o, long offset) { + return testHelper(flag, null, offset, field); + } + + static Object test2(Object o, long offset) { + return UNSAFE.getAndSetObject(o, offset, field); + } + + static public void main(String[] args) { + A a = new A(); + for (int i = 0; i < 20_000; i++) { + testHelper(true, a, F_OFFSET, null); + test1(false, a, F_OFFSET); + test2(a, F_OFFSET); + } + } +} From 11fdca06345542b8d5e54feb1d16f17c2bcb1a82 Mon Sep 17 00:00:00 2001 From: Ivan Walulya Date: Fri, 23 Feb 2024 10:48:50 +0000 Subject: [PATCH 31/38] 8325202: gc/g1/TestMarkStackOverflow.java intermittently crash: G1CMMarkStack::ChunkAllocator::allocate_new_chunk Reviewed-by: tschatzl, ayang --- src/hotspot/share/gc/g1/g1ConcurrentMark.cpp | 23 ++++++++++++++------ src/hotspot/share/gc/g1/g1ConcurrentMark.hpp | 8 ++++--- 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/src/hotspot/share/gc/g1/g1ConcurrentMark.cpp b/src/hotspot/share/gc/g1/g1ConcurrentMark.cpp index cab9f52a009cf..5df9a097cfee7 100644 --- a/src/hotspot/share/gc/g1/g1ConcurrentMark.cpp +++ b/src/hotspot/share/gc/g1/g1ConcurrentMark.cpp @@ -152,7 +152,8 @@ G1CMMarkStack::TaskQueueEntryChunk* G1CMMarkStack::ChunkAllocator::allocate_new_ MutexLocker x(MarkStackChunkList_lock, Mutex::_no_safepoint_check_flag); if (Atomic::load_acquire(&_buckets[bucket]) == nullptr) { - if (!expand()) { + size_t desired_capacity = bucket_size(bucket) * 2; + if (!try_expand_to(desired_capacity)) { return nullptr; } } @@ -196,23 +197,28 @@ bool G1CMMarkStack::ChunkAllocator::initialize(size_t initial_capacity, size_t m return true; } -bool G1CMMarkStack::ChunkAllocator::expand() { +bool G1CMMarkStack::ChunkAllocator::try_expand_to(size_t desired_capacity) { if (_capacity == _max_capacity) { log_debug(gc)("Can not expand overflow mark stack further, already at maximum capacity of " SIZE_FORMAT " chunks.", _capacity); return false; } + size_t old_capacity = _capacity; - // Double capacity if possible. - size_t new_capacity = MIN2(old_capacity * 2, _max_capacity); + desired_capacity = MIN2(desired_capacity, _max_capacity); - if (reserve(new_capacity)) { + if (reserve(desired_capacity)) { log_debug(gc)("Expanded the mark stack capacity from " SIZE_FORMAT " to " SIZE_FORMAT " chunks", - old_capacity, new_capacity); + old_capacity, desired_capacity); return true; } return false; } +bool G1CMMarkStack::ChunkAllocator::try_expand() { + size_t new_capacity = _capacity * 2; + return try_expand_to(new_capacity); +} + G1CMMarkStack::ChunkAllocator::~ChunkAllocator() { if (_buckets == nullptr) { return; @@ -234,6 +240,9 @@ bool G1CMMarkStack::ChunkAllocator::reserve(size_t new_capacity) { size_t highest_bucket = get_bucket(new_capacity - 1); size_t i = get_bucket(_capacity); + // Allocate all buckets associated with indexes between the current capacity (_capacity) + // and the new capacity (new_capacity). This step ensures that there are no gaps in the + // array and that the capacity accurately reflects the reserved memory. for (; i <= highest_bucket; i++) { if (Atomic::load_acquire(&_buckets[i]) != nullptr) { continue; // Skip over already allocated buckets. @@ -261,7 +270,7 @@ bool G1CMMarkStack::ChunkAllocator::reserve(size_t new_capacity) { } void G1CMMarkStack::expand() { - _chunk_allocator.expand(); + _chunk_allocator.try_expand(); } void G1CMMarkStack::add_chunk_to_list(TaskQueueEntryChunk* volatile* list, TaskQueueEntryChunk* elem) { diff --git a/src/hotspot/share/gc/g1/g1ConcurrentMark.hpp b/src/hotspot/share/gc/g1/g1ConcurrentMark.hpp index ccd35bed64f06..c58b99437b840 100644 --- a/src/hotspot/share/gc/g1/g1ConcurrentMark.hpp +++ b/src/hotspot/share/gc/g1/g1ConcurrentMark.hpp @@ -145,7 +145,7 @@ class G1CMMarkStack { // within the bucket. Additionally, each new bucket added to the growable array doubles the capacity of // the growable array. // - // Illustration of the Growable Array data structure. + // Illustration of the growable array data structure. // // +----+ +----+----+ // | |------->| | | @@ -174,7 +174,7 @@ class G1CMMarkStack { size_t bucket_size(size_t bucket) { return (bucket == 0) ? _min_capacity : - _min_capacity * ( 1ULL << (bucket -1)); + _min_capacity * ( 1ULL << (bucket - 1)); } static unsigned int find_highest_bit(uintptr_t mask) { @@ -225,7 +225,9 @@ class G1CMMarkStack { size_t capacity() const { return _capacity; } - bool expand(); + // Expand the mark stack doubling its size. + bool try_expand(); + bool try_expand_to(size_t desired_capacity); TaskQueueEntryChunk* allocate_new_chunk(); }; From ef2d5c40c0d997ba1c5c7eaa50040e8757f06f36 Mon Sep 17 00:00:00 2001 From: Albert Mingkun Yang Date: Fri, 23 Feb 2024 11:47:29 +0000 Subject: [PATCH 32/38] 8326065: Merge Space into ContiguousSpace Reviewed-by: cjplummer, sjohanss --- src/hotspot/share/gc/shared/space.cpp | 16 +-- src/hotspot/share/gc/shared/space.hpp | 124 ++++++------------ src/hotspot/share/gc/shared/vmStructs_gc.hpp | 11 +- .../sun/jvm/hotspot/gc/serial/SerialHeap.java | 4 - .../hotspot/gc/shared/ContiguousSpace.java | 52 +++++--- .../hotspot/gc/shared/GenerationFactory.java | 89 ------------- .../sun/jvm/hotspot/gc/shared/Space.java | 114 ---------------- .../jvm/hotspot/gc/shared/SpaceClosure.java | 29 ---- 8 files changed, 88 insertions(+), 351 deletions(-) delete mode 100644 src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/gc/shared/GenerationFactory.java delete mode 100644 src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/gc/shared/Space.java delete mode 100644 src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/gc/shared/SpaceClosure.java diff --git a/src/hotspot/share/gc/shared/space.cpp b/src/hotspot/share/gc/shared/space.cpp index 31ff8a447c7a5..ca027d60431e2 100644 --- a/src/hotspot/share/gc/shared/space.cpp +++ b/src/hotspot/share/gc/shared/space.cpp @@ -40,7 +40,9 @@ #include "utilities/globalDefinitions.hpp" #include "utilities/macros.hpp" -ContiguousSpace::ContiguousSpace(): Space(), +ContiguousSpace::ContiguousSpace(): + _bottom(nullptr), + _end(nullptr), _next_compaction_space(nullptr), _top(nullptr) { _mangler = new GenSpaceMangler(this); @@ -101,20 +103,14 @@ void ContiguousSpace::mangle_unused_area_complete() { #endif // NOT_PRODUCT -void Space::print_short() const { print_short_on(tty); } +void ContiguousSpace::print_short() const { print_short_on(tty); } -void Space::print_short_on(outputStream* st) const { +void ContiguousSpace::print_short_on(outputStream* st) const { st->print(" space " SIZE_FORMAT "K, %3d%% used", capacity() / K, (int) ((double) used() * 100 / capacity())); } -void Space::print() const { print_on(tty); } - -void Space::print_on(outputStream* st) const { - print_short_on(st); - st->print_cr(" [" PTR_FORMAT ", " PTR_FORMAT ")", - p2i(bottom()), p2i(end())); -} +void ContiguousSpace::print() const { print_on(tty); } void ContiguousSpace::print_on(outputStream* st) const { print_short_on(st); diff --git a/src/hotspot/share/gc/shared/space.hpp b/src/hotspot/share/gc/shared/space.hpp index c60952d7b3816..3a0884229dc98 100644 --- a/src/hotspot/share/gc/shared/space.hpp +++ b/src/hotspot/share/gc/shared/space.hpp @@ -45,35 +45,45 @@ // for iterating over objects and free blocks, etc. // Forward decls. -class Space; class ContiguousSpace; class Generation; class ContiguousSpace; class CardTableRS; class DirtyCardToOopClosure; +class GenSpaceMangler; -// A Space describes a heap area. Class Space is an abstract -// base class. -// -// Space supports allocation, size computation and GC support is provided. +// A space in which the free area is contiguous. It therefore supports +// faster allocation, and compaction. // // Invariant: bottom() and end() are on page_size boundaries and // bottom() <= top() <= end() // top() is inclusive and end() is exclusive. - -class Space: public CHeapObj { +class ContiguousSpace: public CHeapObj { friend class VMStructs; - protected: + +private: HeapWord* _bottom; HeapWord* _end; // Used in support of save_marks() HeapWord* _saved_mark_word; - Space(): - _bottom(nullptr), _end(nullptr) { } + ContiguousSpace* _next_compaction_space; + + HeapWord* _top; + // A helper for mangling the unused area of the space in debug builds. + GenSpaceMangler* _mangler; + + GenSpaceMangler* mangler() { return _mangler; } + + // Allocation helpers (return null if full). + inline HeapWord* allocate_impl(size_t word_size); + inline HeapWord* par_allocate_impl(size_t word_size); + +public: + ContiguousSpace(); + ~ContiguousSpace(); - public: // Accessors HeapWord* bottom() const { return _bottom; } HeapWord* end() const { return _end; } @@ -82,10 +92,6 @@ class Space: public CHeapObj { HeapWord* saved_mark_word() const { return _saved_mark_word; } - // Returns a subregion of the space containing only the allocated objects in - // the space. - virtual MemRegion used_region() const = 0; - // Returns a region that is guaranteed to contain (at least) all objects // allocated at the time of the last call to "save_marks". If the space // initializes its DirtyCardToOopClosure's specifying the "contig" option @@ -98,13 +104,6 @@ class Space: public CHeapObj { return MemRegion(bottom(), saved_mark_word()); } - // For detecting GC bugs. Should only be called at GC boundaries, since - // some unused space may be used as scratch space during GC's. - // We also call this when expanding a space to satisfy an allocation - // request. See bug #4668531 - virtual void mangle_unused_area() = 0; - virtual void mangle_unused_area_complete() = 0; - // Testers bool is_empty() const { return used() == 0; } @@ -123,53 +122,14 @@ class Space: public CHeapObj { bool is_in_reserved(const void* p) const { return _bottom <= p && p < _end; } // Size computations. Sizes are in bytes. - size_t capacity() const { return byte_size(bottom(), end()); } - virtual size_t used() const = 0; - virtual size_t free() const = 0; - - // If "p" is in the space, returns the address of the start of the - // "block" that contains "p". We say "block" instead of "object" since - // some heaps may not pack objects densely; a chunk may either be an - // object or a non-object. If "p" is not in the space, return null. - virtual HeapWord* block_start_const(const void* p) const = 0; - - // Allocation (return null if full). Assumes the caller has established - // mutually exclusive access to the space. - virtual HeapWord* allocate(size_t word_size) = 0; - - // Allocation (return null if full). Enforces mutual exclusion internally. - virtual HeapWord* par_allocate(size_t word_size) = 0; + size_t capacity() const { return byte_size(bottom(), end()); } + size_t used() const { return byte_size(bottom(), top()); } + size_t free() const { return byte_size(top(), end()); } void print() const; virtual void print_on(outputStream* st) const; void print_short() const; void print_short_on(outputStream* st) const; -}; - -class GenSpaceMangler; - -// A space in which the free area is contiguous. It therefore supports -// faster allocation, and compaction. -class ContiguousSpace: public Space { - friend class VMStructs; - -private: - ContiguousSpace* _next_compaction_space; - -protected: - HeapWord* _top; - // A helper for mangling the unused area of the space in debug builds. - GenSpaceMangler* _mangler; - - GenSpaceMangler* mangler() { return _mangler; } - - // Allocation helpers (return null if full). - inline HeapWord* allocate_impl(size_t word_size); - inline HeapWord* par_allocate_impl(size_t word_size); - - public: - ContiguousSpace(); - ~ContiguousSpace(); // Initialization. // "initialize" should be called once on a space, before it is used for @@ -206,19 +166,20 @@ class ContiguousSpace: public Space { bool saved_mark_at_top() const { return saved_mark_word() == top(); } - // In debug mode mangle (write it with a particular bit - // pattern) the unused part of a space. - // Used to save the address in a space for later use during mangling. void set_top_for_allocations(HeapWord* v) PRODUCT_RETURN; // Used to save the space's current top for later use during mangling. void set_top_for_allocations() PRODUCT_RETURN; + // For detecting GC bugs. Should only be called at GC boundaries, since + // some unused space may be used as scratch space during GC's. + // We also call this when expanding a space to satisfy an allocation + // request. See bug #4668531 // Mangle regions in the space from the current top up to the // previously mangled part of the space. - void mangle_unused_area() override PRODUCT_RETURN; + void mangle_unused_area() PRODUCT_RETURN; // Mangle [top, end) - void mangle_unused_area_complete() override PRODUCT_RETURN; + void mangle_unused_area_complete() PRODUCT_RETURN; // Do some sparse checking on the area that should have been mangled. void check_mangled_unused_area(HeapWord* limit) PRODUCT_RETURN; @@ -226,17 +187,13 @@ class ContiguousSpace: public Space { // This code may be null depending on the macro DEBUG_MANGLING. void check_mangled_unused_area_complete() PRODUCT_RETURN; - // Size computations: sizes in bytes. - size_t used() const override { return byte_size(bottom(), top()); } - size_t free() const override { return byte_size(top(), end()); } - - // In a contiguous space we have a more obvious bound on what parts - // contain objects. - MemRegion used_region() const override { return MemRegion(bottom(), top()); } + MemRegion used_region() const { return MemRegion(bottom(), top()); } - // Allocation (return null if full) - HeapWord* allocate(size_t word_size) override; - HeapWord* par_allocate(size_t word_size) override; + // Allocation (return null if full). Assumes the caller has established + // mutually exclusive access to the space. + virtual HeapWord* allocate(size_t word_size); + // Allocation (return null if full). Enforces mutual exclusion internally. + virtual HeapWord* par_allocate(size_t word_size); // Iteration void object_iterate(ObjectClosure* blk); @@ -251,14 +208,15 @@ class ContiguousSpace: public Space { template void oop_since_save_marks_iterate(OopClosureType* blk); - // Very inefficient implementation. - HeapWord* block_start_const(const void* p) const override; + // If "p" is in the space, returns the address of the start of the + // "block" that contains "p". We say "block" instead of "object" since + // some heaps may not pack objects densely; a chunk may either be an + // object or a non-object. If "p" is not in the space, return null. + virtual HeapWord* block_start_const(const void* p) const; // Addresses for inlined allocation HeapWord** top_addr() { return &_top; } - void print_on(outputStream* st) const override; - // Debugging void verify() const; }; diff --git a/src/hotspot/share/gc/shared/vmStructs_gc.hpp b/src/hotspot/share/gc/shared/vmStructs_gc.hpp index 05dbaec5ff531..ae9843c31eb6f 100644 --- a/src/hotspot/share/gc/shared/vmStructs_gc.hpp +++ b/src/hotspot/share/gc/shared/vmStructs_gc.hpp @@ -98,14 +98,13 @@ nonstatic_field(CollectedHeap, _is_gc_active, bool) \ nonstatic_field(CollectedHeap, _total_collections, unsigned int) \ \ + nonstatic_field(ContiguousSpace, _bottom, HeapWord*) \ + nonstatic_field(ContiguousSpace, _end, HeapWord*) \ nonstatic_field(ContiguousSpace, _top, HeapWord*) \ nonstatic_field(ContiguousSpace, _saved_mark_word, HeapWord*) \ \ nonstatic_field(MemRegion, _start, HeapWord*) \ - nonstatic_field(MemRegion, _word_size, size_t) \ - \ - nonstatic_field(Space, _bottom, HeapWord*) \ - nonstatic_field(Space, _end, HeapWord*) + nonstatic_field(MemRegion, _word_size, size_t) #define VM_TYPES_GC(declare_type, \ declare_toplevel_type, \ @@ -135,8 +134,7 @@ /******************************************/ \ \ declare_toplevel_type(CollectedHeap) \ - declare_toplevel_type(Space) \ - declare_type(ContiguousSpace, Space) \ + declare_toplevel_type(ContiguousSpace) \ declare_toplevel_type(BarrierSet) \ declare_type(ModRefBarrierSet, BarrierSet) \ declare_type(CardTableBarrierSet, ModRefBarrierSet) \ @@ -164,7 +162,6 @@ declare_toplevel_type(HeapWord*) \ declare_toplevel_type(HeapWord* volatile) \ declare_toplevel_type(MemRegion*) \ - declare_toplevel_type(Space*) \ declare_toplevel_type(ThreadLocalAllocBuffer*) \ \ declare_toplevel_type(BarrierSet::FakeRtti) diff --git a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/gc/serial/SerialHeap.java b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/gc/serial/SerialHeap.java index 2640da35ac1c8..397d0e0b7620e 100644 --- a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/gc/serial/SerialHeap.java +++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/gc/serial/SerialHeap.java @@ -46,8 +46,6 @@ public CollectedHeapName kind() { private static AddressField youngGenField; private static AddressField oldGenField; - private static GenerationFactory genFactory; - static { VM.registerVMInitializedObserver(new Observer() { public void update(Observable o, Object data) { @@ -61,8 +59,6 @@ private static synchronized void initialize(TypeDataBase db) { youngGenField = type.getAddressField("_young_gen"); oldGenField = type.getAddressField("_old_gen"); - - genFactory = new GenerationFactory(); } public DefNewGeneration youngGen() { diff --git a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/gc/shared/ContiguousSpace.java b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/gc/shared/ContiguousSpace.java index 20881a9300bac..d6bf39775bba5 100644 --- a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/gc/shared/ContiguousSpace.java +++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/gc/shared/ContiguousSpace.java @@ -34,7 +34,17 @@ import sun.jvm.hotspot.utilities.Observable; import sun.jvm.hotspot.utilities.Observer; -public class ContiguousSpace extends Space implements LiveRegionsProvider { +/**

A ContiguousSpace describes a heap area.

+ +

Invariant: bottom() and end() are on page_size boundaries and:

+ +

bottom() <= top() <= end()

+ +

top() is inclusive and end() is exclusive.

*/ + +public class ContiguousSpace extends VMObject implements LiveRegionsProvider { + private static AddressField bottomField; + private static AddressField endField; private static AddressField topField; static { @@ -48,6 +58,8 @@ public void update(Observable o, Object data) { private static synchronized void initialize(TypeDataBase db) { Type type = db.lookupType("ContiguousSpace"); + bottomField = type.getAddressField("_bottom"); + endField = type.getAddressField("_end"); topField = type.getAddressField("_top"); } @@ -55,24 +67,30 @@ public ContiguousSpace(Address addr) { super(addr); } - public Address top() { - return topField.getValue(addr); - } + public Address bottom() { return bottomField.getValue(addr); } + public Address end() { return endField.getValue(addr); } + public Address top() { return topField.getValue(addr); } - /** In bytes */ - public long capacity() { - return end().minus(bottom()); + /** Support for iteration over heap -- not sure how this will + interact with GC in reflective system, but necessary for the + debugging mechanism */ + public OopHandle bottomAsOopHandle() { + return bottomField.getOopHandle(addr); } - /** In bytes */ - public long used() { - return top().minus(bottom()); + /** Support for iteration over heap -- not sure how this will + interact with GC in reflective system, but necessary for the + debugging mechanism */ + public OopHandle nextOopHandle(OopHandle handle, long size) { + return handle.addOffsetToAsOopHandle(size); } - /** In bytes */ - public long free() { - return end().minus(top()); - } + /** Returned value is in bytes */ + public long capacity() { return end().minus(bottom()); } + public long used() { return top().minus(bottom()); } + public long free() { return end().minus(top()); } + + public void print() { printOn(System.out); } /** In a contiguous space we have a more obvious bound on what parts contain objects. */ @@ -95,6 +113,10 @@ public boolean contains(Address p) { public void printOn(PrintStream tty) { tty.print(" [" + bottom() + "," + top() + "," + end() + ")"); - super.printOn(tty); + tty.print(" space capacity = "); + tty.print(capacity()); + tty.print(", "); + tty.print((double) used() * 100.0/ capacity()); + tty.print(" used"); } } diff --git a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/gc/shared/GenerationFactory.java b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/gc/shared/GenerationFactory.java deleted file mode 100644 index 307054e5a4ee2..0000000000000 --- a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/gc/shared/GenerationFactory.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright (c) 2000, 2020, Oracle and/or its affiliates. All rights reserved. - * 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 sun.jvm.hotspot.gc.shared; - -import java.util.*; - -import sun.jvm.hotspot.debugger.*; -import sun.jvm.hotspot.gc.serial.*; -import sun.jvm.hotspot.runtime.*; -import sun.jvm.hotspot.types.*; -import sun.jvm.hotspot.utilities.Observable; -import sun.jvm.hotspot.utilities.Observer; - -/** Factory containing a VirtualConstructor suitable for instantiating - wrapper objects for all types of generations */ - -public class GenerationFactory { - private static VirtualConstructor ctor; - - static { - VM.registerVMInitializedObserver(new Observer() { - public void update(Observable o, Object data) { - initialize(VM.getVM().getTypeDataBase()); - } - }); - } - - private static synchronized void initialize(TypeDataBase db) { - ctor = new VirtualConstructor(db); - - ctor.addMapping("DefNewGeneration", DefNewGeneration.class); - ctor.addMapping("TenuredGeneration", TenuredGeneration.class); - } - - public static Generation newObject(Address addr) { - try { - return (Generation) ctor.instantiateWrapperFor(addr); - } catch (WrongTypeException e) { - return new Generation(addr) { - public String name() { - return "unknown generation type"; - } - public void spaceIterate(SpaceClosure blk, boolean usedOnly) { - } - public void liveRegionsIterate(LiveRegionsClosure closure) { - } - public void printOn(java.io.PrintStream tty) { - tty.println("unknown subtype of Generation @ " + getAddress() + " (" + - virtualSpace().low() + "," + virtualSpace().high() + ")"); - } - public long used() { - return 0; - } - public long free() { - return 0; - } - public long capacity() { - return 0; - } - public long contiguousAvailable() { - return 0; - } - - }; - } - } -} diff --git a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/gc/shared/Space.java b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/gc/shared/Space.java deleted file mode 100644 index 3318d8bf27063..0000000000000 --- a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/gc/shared/Space.java +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright (c) 2000, 2020, Oracle and/or its affiliates. All rights reserved. - * 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 sun.jvm.hotspot.gc.shared; - -import java.io.*; -import java.util.*; -import sun.jvm.hotspot.debugger.*; -import sun.jvm.hotspot.memory.*; -import sun.jvm.hotspot.runtime.*; -import sun.jvm.hotspot.types.*; -import sun.jvm.hotspot.utilities.Observable; -import sun.jvm.hotspot.utilities.Observer; - -/**

A Space describes a heap area. Class Space is an abstract base - class.

- -

Space supports allocation, size computation and GC support is - provided.

- -

Invariant: bottom() and end() are on page_size boundaries and:

- -

bottom() <= top() <= end()

- -

top() is inclusive and end() is exclusive.

*/ - -public abstract class Space extends VMObject { - private static AddressField bottomField; - private static AddressField endField; - - static { - VM.registerVMInitializedObserver(new Observer() { - public void update(Observable o, Object data) { - initialize(VM.getVM().getTypeDataBase()); - } - }); - } - - private static synchronized void initialize(TypeDataBase db) { - Type type = db.lookupType("Space"); - - bottomField = type.getAddressField("_bottom"); - endField = type.getAddressField("_end"); - } - - public Space(Address addr) { - super(addr); - } - - public Address bottom() { return bottomField.getValue(addr); } - public Address end() { return endField.getValue(addr); } - - /** Returns a subregion of the space containing all the objects in - the space. */ - public MemRegion usedRegion() { - return new MemRegion(bottom(), end()); - } - - /** Support for iteration over heap -- not sure how this will - interact with GC in reflective system, but necessary for the - debugging mechanism */ - public OopHandle bottomAsOopHandle() { - return bottomField.getOopHandle(addr); - } - - /** Support for iteration over heap -- not sure how this will - interact with GC in reflective system, but necessary for the - debugging mechanism */ - public OopHandle nextOopHandle(OopHandle handle, long size) { - return handle.addOffsetToAsOopHandle(size); - } - - /** Returned value is in bytes */ - public long capacity() { return end().minus(bottom()); } - /** Returned value is in bytes */ - public abstract long used(); - /** Returned value is in bytes */ - public abstract long free(); - - /** Testers */ - public boolean contains(Address p) { - return (bottom().lessThanOrEqual(p) && end().greaterThan(p)); - } - - public void print() { printOn(System.out); } - public void printOn(PrintStream tty) { - tty.print(" space capacity = "); - tty.print(capacity()); - tty.print(", "); - tty.print((double) used() * 100.0/ capacity()); - tty.print(" used"); - } -} diff --git a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/gc/shared/SpaceClosure.java b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/gc/shared/SpaceClosure.java deleted file mode 100644 index c8ef40f98ab9b..0000000000000 --- a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/gc/shared/SpaceClosure.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright (c) 2000, 2015, Oracle and/or its affiliates. All rights reserved. - * 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 sun.jvm.hotspot.gc.shared; - -public interface SpaceClosure { - public void doSpace(Space s); -} From 09a78b5da961f3575cf20c9b56bff86ddbd6545d Mon Sep 17 00:00:00 2001 From: Martin Doerr Date: Fri, 23 Feb 2024 13:52:10 +0000 Subject: [PATCH 33/38] 8326378: [PPC64] CodeEntryAlignment too large Reviewed-by: shade, lucy --- src/hotspot/cpu/ppc/globals_ppc.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hotspot/cpu/ppc/globals_ppc.hpp b/src/hotspot/cpu/ppc/globals_ppc.hpp index f46ca5db3b7df..a2a94c178fb8b 100644 --- a/src/hotspot/cpu/ppc/globals_ppc.hpp +++ b/src/hotspot/cpu/ppc/globals_ppc.hpp @@ -60,7 +60,7 @@ define_pd_global(bool, VMContinuations, true); // Use large code-entry alignment. define_pd_global(uintx, CodeCacheSegmentSize, 128); -define_pd_global(intx, CodeEntryAlignment, 128); +define_pd_global(intx, CodeEntryAlignment, 64); define_pd_global(intx, OptoLoopAlignment, 16); define_pd_global(intx, InlineSmallCode, 1500); From c26c5833ccd7bdfd3f8ed9da76334d5b4e6e55ca Mon Sep 17 00:00:00 2001 From: Weijun Wang Date: Fri, 23 Feb 2024 16:10:44 +0000 Subject: [PATCH 34/38] 8311003: missing @since info in jdk.security.jgss Reviewed-by: mullan --- .../classes/com/sun/security/jgss/AuthorizationDataEntry.java | 4 +++- .../classes/com/sun/security/jgss/ExtendedGSSContext.java | 4 +++- .../share/classes/com/sun/security/jgss/GSSUtil.java | 4 +++- .../com/sun/security/jgss/InquireSecContextPermission.java | 4 +++- .../share/classes/com/sun/security/jgss/InquireType.java | 4 +++- .../share/classes/com/sun/security/jgss/package-info.java | 2 ++ 6 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/jdk.security.jgss/share/classes/com/sun/security/jgss/AuthorizationDataEntry.java b/src/jdk.security.jgss/share/classes/com/sun/security/jgss/AuthorizationDataEntry.java index eabaac94ad539..3bb51f1ccb29f 100644 --- a/src/jdk.security.jgss/share/classes/com/sun/security/jgss/AuthorizationDataEntry.java +++ b/src/jdk.security.jgss/share/classes/com/sun/security/jgss/AuthorizationDataEntry.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009, 2013, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2009, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -27,6 +27,8 @@ /** * Kerberos 5 AuthorizationData entry. + * + * @since 1.7 */ public final class AuthorizationDataEntry { diff --git a/src/jdk.security.jgss/share/classes/com/sun/security/jgss/ExtendedGSSContext.java b/src/jdk.security.jgss/share/classes/com/sun/security/jgss/ExtendedGSSContext.java index 7011f5d8dc00b..8daa300ea5dad 100644 --- a/src/jdk.security.jgss/share/classes/com/sun/security/jgss/ExtendedGSSContext.java +++ b/src/jdk.security.jgss/share/classes/com/sun/security/jgss/ExtendedGSSContext.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009, 2013, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2009, 2024, Oracle and/or its affiliates. All rights reserved. * 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,8 @@ * The extended GSSContext interface for supporting additional * functionalities not defined by {@code org.ietf.jgss.GSSContext}, * such as querying context-specific attributes. + * + * @since 1.7 */ public interface ExtendedGSSContext extends GSSContext { diff --git a/src/jdk.security.jgss/share/classes/com/sun/security/jgss/GSSUtil.java b/src/jdk.security.jgss/share/classes/com/sun/security/jgss/GSSUtil.java index b94e2877f0800..0aca9771d6d00 100644 --- a/src/jdk.security.jgss/share/classes/com/sun/security/jgss/GSSUtil.java +++ b/src/jdk.security.jgss/share/classes/com/sun/security/jgss/GSSUtil.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -32,6 +32,8 @@ /** * GSS-API Utilities for using in conjunction with Sun Microsystem's * implementation of Java GSS-API. + * + * @since 1.4 */ public class GSSUtil { /** diff --git a/src/jdk.security.jgss/share/classes/com/sun/security/jgss/InquireSecContextPermission.java b/src/jdk.security.jgss/share/classes/com/sun/security/jgss/InquireSecContextPermission.java index 618d3c74752f2..33ac51e8d51db 100644 --- a/src/jdk.security.jgss/share/classes/com/sun/security/jgss/InquireSecContextPermission.java +++ b/src/jdk.security.jgss/share/classes/com/sun/security/jgss/InquireSecContextPermission.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009, 2013, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2009, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -34,6 +34,8 @@ * method. * *

The target name is the {@link InquireType} allowed. + * + * @since 1.7 */ public final class InquireSecContextPermission extends BasicPermission { private static final long serialVersionUID = -7131173349668647297L; diff --git a/src/jdk.security.jgss/share/classes/com/sun/security/jgss/InquireType.java b/src/jdk.security.jgss/share/classes/com/sun/security/jgss/InquireType.java index 6240f0d3ddfec..b7f285bf836de 100644 --- a/src/jdk.security.jgss/share/classes/com/sun/security/jgss/InquireType.java +++ b/src/jdk.security.jgss/share/classes/com/sun/security/jgss/InquireType.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009, 2013, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2009, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -28,6 +28,8 @@ /** * Attribute types that can be specified as an argument of * {@link com.sun.security.jgss.ExtendedGSSContext#inquireSecContext} + * + * @since 1.7 */ public enum InquireType { /** diff --git a/src/jdk.security.jgss/share/classes/com/sun/security/jgss/package-info.java b/src/jdk.security.jgss/share/classes/com/sun/security/jgss/package-info.java index bb5a4f2b38c4a..b7010b2e740a4 100644 --- a/src/jdk.security.jgss/share/classes/com/sun/security/jgss/package-info.java +++ b/src/jdk.security.jgss/share/classes/com/sun/security/jgss/package-info.java @@ -26,5 +26,7 @@ /** * This package defines classes and interfaces for the JDK extensions * to the GSS-API. + * + * @since 1.4 */ package com.sun.security.jgss; From 27574b384cb5c46358a8bba1bffa8d57d85f6670 Mon Sep 17 00:00:00 2001 From: Magnus Ihse Bursie Date: Fri, 23 Feb 2024 17:31:13 +0000 Subject: [PATCH 35/38] 8326585: COMPARE_BUILD=PATCH fails if patch -R fails Reviewed-by: erikj --- make/InitSupport.gmk | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/make/InitSupport.gmk b/make/InitSupport.gmk index 9ea01d375ced9..4b14c4f9ad951 100644 --- a/make/InitSupport.gmk +++ b/make/InitSupport.gmk @@ -1,5 +1,5 @@ # -# Copyright (c) 2011, 2022, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2011, 2024, Oracle and/or its affiliates. All rights reserved. # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. # # This code is free software; you can redistribute it and/or modify it @@ -427,8 +427,9 @@ else # $(HAS_SPEC)=true # Cleanup after a compare build define CleanupCompareBuild - # If running with a COMPARE_BUILD patch, reverse-apply it - $(if $(COMPARE_BUILD_PATCH), cd $(topdir) && $(PATCH) -R -p1 < $(COMPARE_BUILD_PATCH)) + # If running with a COMPARE_BUILD patch, reverse-apply it, but continue + # even if that fails (can happen with removed files). + $(if $(COMPARE_BUILD_PATCH), cd $(topdir) && $(PATCH) -R -p1 < $(COMPARE_BUILD_PATCH) || true) # Move this build away and restore the original build $(MKDIR) -p $(topdir)/build/compare-build $(MV) $(OUTPUTDIR) $(COMPARE_BUILD_OUTPUTDIR) From 63f6a563a3987d74ef673718d5209cc7c469751c Mon Sep 17 00:00:00 2001 From: Joe Darcy Date: Fri, 23 Feb 2024 18:03:02 +0000 Subject: [PATCH 36/38] 8326530: Widen allowable error bound of Math.tan Reviewed-by: bpb, rgiulietti --- src/java.base/share/classes/java/lang/Math.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/java.base/share/classes/java/lang/Math.java b/src/java.base/share/classes/java/lang/Math.java index 11c636c8434f8..fa45b6436c752 100644 --- a/src/java.base/share/classes/java/lang/Math.java +++ b/src/java.base/share/classes/java/lang/Math.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1994, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1994, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -212,7 +212,7 @@ public static double cos(double a) { *

  • If the argument is zero, then the result is a zero with the * same sign as the argument. * - *

    The computed result must be within 1 ulp of the exact result. + *

    The computed result must be within 1.25 ulps of the exact result. * Results must be semi-monotonic. * * @param a an angle, in radians. From d10f277bd39bb5ac9bd48939c916de607fef8ace Mon Sep 17 00:00:00 2001 From: Leonid Mesnik Date: Fri, 23 Feb 2024 19:58:38 +0000 Subject: [PATCH 37/38] 8326006: Allow TEST_VM_FLAGLESS to set flagless mode Reviewed-by: tschatzl, ayang --- test/jtreg-ext/requires/VMProps.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/test/jtreg-ext/requires/VMProps.java b/test/jtreg-ext/requires/VMProps.java index 986e0ec14bb20..12792181b6144 100644 --- a/test/jtreg-ext/requires/VMProps.java +++ b/test/jtreg-ext/requires/VMProps.java @@ -652,14 +652,15 @@ private String jdkContainerized() { * Checks if we are in almost out-of-box configuration, i.e. the flags * which JVM is started with don't affect its behavior "significantly". * {@code TEST_VM_FLAGLESS} enviroment variable can be used to force this - * method to return true and allow any flags. + * method to return true or false and allow or reject any flags. * * @return true if there are no JVM flags */ private String isFlagless() { boolean result = true; - if (System.getenv("TEST_VM_FLAGLESS") != null) { - return "" + result; + String flagless = System.getenv("TEST_VM_FLAGLESS"); + if (flagless != null) { + return "" + "true".equalsIgnoreCase(flagless); } List allFlags = allFlags().toList(); From 1799ffeaa9baa7d703c7acc8d8738211694f946e Mon Sep 17 00:00:00 2001 From: Korov Date: Sun, 25 Feb 2024 11:52:19 +0000 Subject: [PATCH 38/38] 8310351: Typo in ImmutableCollections Reviewed-by: jlaskey, jpai --- src/java.base/share/classes/java/util/ImmutableCollections.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/java.base/share/classes/java/util/ImmutableCollections.java b/src/java.base/share/classes/java/util/ImmutableCollections.java index 398ebb14a54da..525cdf15ecd9f 100644 --- a/src/java.base/share/classes/java/util/ImmutableCollections.java +++ b/src/java.base/share/classes/java/util/ImmutableCollections.java @@ -161,7 +161,7 @@ abstract static class AbstractImmutableCollection extends AbstractCollection< * Null argument or null elements in the argument will result in NPE. * * @param the List's element type - * @param input the input array + * @param coll the input collection * @return the new list */ @SuppressWarnings("unchecked")