diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index f1fe97d9dcf..7d2b67ce6bb 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -153,7 +153,7 @@ jobs: apt-architecture: 'i386' # Some multilib libraries do not have proper inter-dependencies, so we have to # install their dependencies manually. - apt-extra-packages: 'libfreetype-dev:i386 libtiff-dev:i386 libcupsimage2-dev:i386 libc6-i386 libgcc-s1:i386 libstdc++6:i386 libffi-dev:i386' + apt-extra-packages: 'libfreetype-dev:i386 libtiff-dev:i386 libcupsimage2-dev:i386 libffi-dev:i386' extra-conf-options: '--with-target-bits=32 --enable-fallback-linker --enable-libffi-bundling' configure-arguments: ${{ github.event.inputs.configure-arguments }} make-arguments: ${{ github.event.inputs.make-arguments }} diff --git a/make/GenerateLinkOptData.gmk b/make/GenerateLinkOptData.gmk index 576bc4190b9..5052e4c0358 100644 --- a/make/GenerateLinkOptData.gmk +++ b/make/GenerateLinkOptData.gmk @@ -62,6 +62,15 @@ ifeq ($(EXTERNAL_BUILDJDK), true) INTERIM_IMAGE_DIR := $(BUILD_JDK) endif +# These are needed for deterministic classlist: +# - The classlist can be influenced by locale. Always set it to en/US. +# - Run with -Xint, as the compiler can speculatively resolve constant pool entries. +# - ForkJoinPool parallelism can cause constant pool resolution to be non-deterministic. +CLASSLIST_FILE_VM_OPTS = \ + -Duser.language=en -Duser.country=US \ + -Xint \ + -Djava.util.concurrent.ForkJoinPool.common.parallelism=0 + # Save the stderr output of the command and print it along with stdout in case # something goes wrong. $(CLASSLIST_FILE): $(INTERIM_IMAGE_DIR)/bin/java$(EXECUTABLE_SUFFIX) $(CLASSLIST_JAR) @@ -69,7 +78,7 @@ $(CLASSLIST_FILE): $(INTERIM_IMAGE_DIR)/bin/java$(EXECUTABLE_SUFFIX) $(CLASSLIST $(call LogInfo, Generating $(patsubst $(OUTPUTDIR)/%, %, $@)) $(call LogInfo, Generating $(patsubst $(OUTPUTDIR)/%, %, $(JLI_TRACE_FILE))) $(FIXPATH) $(INTERIM_IMAGE_DIR)/bin/java -XX:DumpLoadedClassList=$@.raw \ - -Duser.language=en -Duser.country=US \ + $(CLASSLIST_FILE_VM_OPTS) \ -cp $(SUPPORT_OUTPUTDIR)/classlist.jar \ build.tools.classlist.HelloClasslist $(LOG_DEBUG) $(GREP) -v HelloClasslist $@.raw > $@.interim @@ -79,7 +88,7 @@ $(CLASSLIST_FILE): $(INTERIM_IMAGE_DIR)/bin/java$(EXECUTABLE_SUFFIX) $(CLASSLIST $(FIXPATH) $(INTERIM_IMAGE_DIR)/bin/java -XX:DumpLoadedClassList=$@.raw.2 \ -XX:SharedClassListFile=$@.interim -XX:SharedArchiveFile=$@.jsa \ -Djava.lang.invoke.MethodHandle.TRACE_RESOLVE=true \ - -Duser.language=en -Duser.country=US \ + $(CLASSLIST_FILE_VM_OPTS) \ --module-path $(SUPPORT_OUTPUTDIR)/classlist.jar \ -cp $(SUPPORT_OUTPUTDIR)/classlist.jar \ build.tools.classlist.HelloClasslist \ diff --git a/make/autoconf/flags-cflags.m4 b/make/autoconf/flags-cflags.m4 index 09395202f22..da63a6dba06 100644 --- a/make/autoconf/flags-cflags.m4 +++ b/make/autoconf/flags-cflags.m4 @@ -502,12 +502,12 @@ AC_DEFUN([FLAGS_SETUP_CFLAGS_HELPER], elif test "x$TOOLCHAIN_TYPE" = xclang; then ALWAYS_DEFINES_JVM="-D_GNU_SOURCE" elif test "x$TOOLCHAIN_TYPE" = xmicrosoft; then - # Access APIs for Windows 8 and above - # see https://docs.microsoft.com/en-us/cpp/porting/modifying-winver-and-win32-winnt?view=msvc-170 - ALWAYS_DEFINES_JDK="-DWIN32_LEAN_AND_MEAN -D_WIN32_WINNT=0x0602 \ - -D_CRT_SECURE_NO_WARNINGS -D_CRT_NONSTDC_NO_DEPRECATE -DWIN32 -DIAL" - ALWAYS_DEFINES_JVM="-DNOMINMAX -DWIN32_LEAN_AND_MEAN -D_WIN32_WINNT=0x0602 \ - -D_CRT_SECURE_NO_WARNINGS -D_CRT_NONSTDC_NO_DEPRECATE" + # _WIN32_WINNT=0x0602 means access APIs for Windows 8 and above. See + # https://docs.microsoft.com/en-us/cpp/porting/modifying-winver-and-win32-winnt?view=msvc-170 + ALWAYS_DEFINES="-DWIN32_LEAN_AND_MEAN -D_WIN32_WINNT=0x0602 \ + -D_CRT_DECLARE_NONSTDC_NAMES -D_CRT_NONSTDC_NO_WARNINGS -D_CRT_SECURE_NO_WARNINGS" + ALWAYS_DEFINES_JDK="$ALWAYS_DEFINES -DWIN32 -DIAL" + ALWAYS_DEFINES_JVM="$ALWAYS_DEFINES -DNOMINMAX" fi ############################################################################### diff --git a/make/autoconf/lib-tests.m4 b/make/autoconf/lib-tests.m4 index be099e50a04..955b8c9ba83 100644 --- a/make/autoconf/lib-tests.m4 +++ b/make/autoconf/lib-tests.m4 @@ -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 @@ -28,7 +28,7 @@ ################################################################################ # Minimum supported versions -JTREG_MINIMUM_VERSION=7.3.1 +JTREG_MINIMUM_VERSION=7.4 GTEST_MINIMUM_VERSION=1.14.0 ############################################################################### diff --git a/make/conf/github-actions.conf b/make/conf/github-actions.conf index 86fd1feccb3..2bbd943591d 100644 --- a/make/conf/github-actions.conf +++ b/make/conf/github-actions.conf @@ -26,7 +26,7 @@ # Versions and download locations for dependencies used by GitHub Actions (GHA) GTEST_VERSION=1.14.0 -JTREG_VERSION=7.3.1+1 +JTREG_VERSION=7.4+1 LINUX_X64_BOOT_JDK_EXT=tar.gz LINUX_X64_BOOT_JDK_URL=https://github.com/SAP/SapMachine/releases/download/sapmachine-22.0.1/sapmachine-jdk-22.0.1_linux-x64_bin.tar.gz diff --git a/make/conf/jib-profiles.js b/make/conf/jib-profiles.js index b6f091398d5..30c45d4cde1 100644 --- a/make/conf/jib-profiles.js +++ b/make/conf/jib-profiles.js @@ -1184,9 +1184,9 @@ var getJibProfilesDependencies = function (input, common) { jtreg: { server: "jpg", product: "jtreg", - version: "7.3.1", + version: "7.4", build_number: "1", - file: "bundles/jtreg-7.3.1+1.zip", + file: "bundles/jtreg-7.4+1.zip", environment_name: "JT_HOME", environment_path: input.get("jtreg", "home_path") + "/bin", configure_args: "--with-jtreg=" + input.get("jtreg", "home_path"), diff --git a/make/modules/java.desktop/lib/ClientLibraries.gmk b/make/modules/java.desktop/lib/ClientLibraries.gmk index 6f3616608cc..f0239695369 100644 --- a/make/modules/java.desktop/lib/ClientLibraries.gmk +++ b/make/modules/java.desktop/lib/ClientLibraries.gmk @@ -281,6 +281,7 @@ endif ifeq ($(USE_EXTERNAL_HARFBUZZ), true) LIBFONTMANAGER_EXTRA_SRC = LIBFONTMANAGER_LIBS += $(HARFBUZZ_LIBS) + LIBFONTMANAGER_CFLAGS += $(HARFBUZZ_CFLAGS) else LIBFONTMANAGER_EXTRA_SRC = libharfbuzz diff --git a/make/scripts/update_copyright_year.sh b/make/scripts/update_copyright_year.sh index a825486a941..bb61d48c91c 100644 --- a/make/scripts/update_copyright_year.sh +++ b/make/scripts/update_copyright_year.sh @@ -1,7 +1,7 @@ #!/bin/bash -f # -# Copyright (c) 2010, 2022, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2010, 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,8 +27,10 @@ # (Originally from xdono, Thanks!) #------------------------------------------------------------ -copyright="Copyright (c)" +copyright="Copyright" +copyright_symbol="(c)" company="Oracle" +year=`date +%Y` #------------------------------------------------------------ awk="awk" @@ -49,66 +51,75 @@ rm -f -r ${tmp} mkdir -p ${tmp} total=0 -# Default or supplied company name -if [ "$3" != "" ] ; then - company="$3" -fi - -# This year or supplied year -if [ "$2" != "" ] ; then - year="$2" -else - year=`date +%Y` -fi - -# VCS select -vcs="$1" +usage="Usage: `basename "$0"` [-c company] [-y year] [-h|f]" +Help() +{ + # Display Help + echo "Updates the Copyright year range in Git sources." + echo + echo "By default, the tool limits the processed changesets " + echo "to those in the current branch and the current year." + echo + echo "Note, cancelling the script will skip cleanup in /tmp." + echo + echo $usage + echo "options:" + echo "-c Specifies the company. Set to Oracle by default." + echo "-y Specifies the copyright year. Set to current year by default." + echo "-f Updates the copyright for all change sets in a given year," + echo " as specified by -y." + echo "-h Print this help." + echo +} -if [ -z "$vcs" ] ; then - git_found=false - hg_found=false +full_year=false - [ -d "${this_script_dir}/../../.git" ] && git_found=true - [ -d "${this_script_dir}/../../.hg" ] && hg_found=true +# Process options +while getopts "c:fhy:" option; do + case $option in + c) # supplied company year + company=${OPTARG} + ;; + f) # update all change sets in a full year + full_year=true + ;; + h) # display help + Help + exit 0 + ;; + y) # supplied company year + year=${OPTARG} + ;; + \?) # illegal option + echo "$usage" + exit 1 + ;; + esac +done - if [ "$git_found" == "true" ] && [ "$hg_found" == "false" ] ; then - vcs="git" - elif [ "$hg_found" == "true" ] && [ "$git_found" == "false" ] ; then - vcs="hg" +# VCS check +git_found=false +[ -d "${this_script_dir}/../../.git" ] && git_found=true +if [ "$git_found" != "true" ]; then + echo "Error: Please execute script from within make/scripts." + exit 1 +else + echo "Using Git version control system" + vcs_status=(git ls-files -m) + if [ "$full_year" = "true" ]; then + vcs_list_changesets=(git log --no-merges --since="${year}-01-01T00:00:00Z" --until="${year}-12-31T23:59:59Z" --pretty=tformat:"%H") else - echo "Error: could not auto-detect version control system" - vcs="" + vcs_list_changesets=(git log --no-merges 'master..HEAD' --since="${year}-01-01T00:00:00Z" --until="${year}-12-31T23:59:59Z" --pretty=tformat:"%H") fi + vcs_changeset_message=(git log -1 --pretty=tformat:"%B") # followed by ${changeset} + vcs_changeset_files=(git diff-tree --no-commit-id --name-only -r) # followed by ${changeset} fi -case "$vcs" in - "git") - echo "Using Git version control system" - vcs_status=(git ls-files -m) - vcs_list_changesets=(git log --no-merges --since="${year}-01-01T00:00:00Z" --until="${year}-12-31T23:59:59Z" --pretty=tformat:"%H") - vcs_changeset_message=(git log -1 --pretty=tformat:"%B") # followed by ${changeset} - vcs_changeset_files=(git diff-tree --no-commit-id --name-only -r) # followed by ${changeset} - ;; - - "hg") - echo "Using Mercurial version control system" - vcs_status=(hg status) - vcs_list_changesets=(hg log --no-merges -v -d "${year}-01-01 to ${year}-12-31" --template '{node}\n') - vcs_changeset_message=(hg log -l1 --template '{desc}\n' --rev) # followed by ${changeset} - vcs_changeset_files=(hg log -l1 -v --template '{files}\n' --rev) # followed by ${changeset} - ;; - - *) - echo "Usage: `basename "$0"` [year [company]]" - exit 1 - ;; -esac - # Return true if it makes sense to edit this file saneFileToCheck() { if [ "$1" != "" -a -f $1 ] ; then - isText=`file "$1" | egrep -i '(text|source)' | cat` + isText=`file "$1" | grep -i -E '(text|source)' | cat` hasCopyright=`grep 'Copyright' "$1" | cat` lastLineCount=`tail -1 "$1" | wc -l` if [ "${isText}" != "" \ @@ -131,9 +142,13 @@ updateFile() # file rm -f $1.OLD mv $1 $1.OLD cat $1.OLD | \ - sed -e "s@\(${copyright} [12][0-9][0-9][0-9],\) [12][0-9][0-9][0-9], ${company}@\1 ${year}, ${company}@" | \ - sed -e "s@\(${copyright} [12][0-9][0-9][0-9],\) ${company}@\1 ${year}, ${company}@" | \ - sed -e "s@${copyright} ${year}, ${year}, ${company}@${copyright} ${year}, ${company}@" \ + sed -e "s@\(${copyright} \(${copyright_symbol} \)\{0,1\}[12][0-9][0-9][0-9],\) [12][0-9][0-9][0-9], ${company}@\1 ${year}, ${company}@" | \ + sed -e "s@\(${copyright} \(${copyright_symbol} \)\{0,1\}[12][0-9][0-9][0-9],\) [12][0-9][0-9][0-9] ${company}@\1 ${year} ${company}@" | \ + sed -e "s@\(${copyright} \(${copyright_symbol} \)\{0,1\}[12][0-9][0-9][0-9],\) ${company}@\1 ${year}, ${company}@" | \ + sed -e "s@\(${copyright} \(${copyright_symbol} \)\{0,1\}[12][0-9][0-9][0-9],\) ${company}@\1, ${year}, ${company}@" | \ + sed -e "s@\(${copyright} \(${copyright_symbol} \)\{0,1\}[12][0-9][0-9][0-9]\) ${company}@\1, ${year} ${company}@" | \ + sed -e "s@${copyright} ${year}, ${year}, ${company}@${copyright} ${year}, ${company}@" | \ + sed -e "s@${copyright} ${copyright_symbol} ${year}, ${year}, ${company}@${copyright} ${copyright_symbol} ${year}, ${company}@" \ > $1 if ! diff -b -w $1.OLD $1 > /dev/null ; then \ changed="true" @@ -205,19 +220,19 @@ if [ -s ${all_changesets} ] ; then "${vcs_changeset_message[@]}" "${changeset}" > ${desc} printf "%d: %s\n%s\n" ${index} "${changeset}" "`cat ${desc}|head -1`" if [ "${year}" = "2010" ] ; then - if cat ${desc} | fgrep -i "Added tag" > /dev/null ; then + if cat ${desc} | grep -i -F "Added tag" > /dev/null ; then printf " EXCLUDED tag changeset.\n" - elif cat ${desc} | fgrep -i rebrand > /dev/null ; then + elif cat ${desc} | grep -i -F rebrand > /dev/null ; then printf " EXCLUDED rebrand changeset.\n" - elif cat ${desc} | fgrep -i copyright > /dev/null ; then + elif cat ${desc} | grep -i -F copyright > /dev/null ; then printf " EXCLUDED copyright changeset.\n" else updateChangesetFiles ${changeset} fi else - if cat ${desc} | fgrep -i "Added tag" > /dev/null ; then + if cat ${desc} | grep -i -F "Added tag" > /dev/null ; then printf " EXCLUDED tag changeset.\n" - elif cat ${desc} | fgrep -i "copyright year" > /dev/null ; then + elif cat ${desc} | grep -i -F "copyright year" > /dev/null ; then printf " EXCLUDED copyright year changeset.\n" else updateChangesetFiles ${changeset} diff --git a/src/hotspot/cpu/ppc/macroAssembler_ppc.cpp b/src/hotspot/cpu/ppc/macroAssembler_ppc.cpp index 6aaa7f3c057..0527cb306b2 100644 --- a/src/hotspot/cpu/ppc/macroAssembler_ppc.cpp +++ b/src/hotspot/cpu/ppc/macroAssembler_ppc.cpp @@ -2130,6 +2130,295 @@ void MacroAssembler::check_klass_subtype(Register sub_klass, bind(L_failure); // Fallthru if not successful. } +// scans count pointer sized words at [addr] for occurrence of value, +// generic (count must be >0) +// iff found: CR0 eq, scratch == 0 +void MacroAssembler::repne_scan(Register addr, Register value, Register count, Register scratch) { + Label Lloop, Lexit; + +#ifdef ASSERT + { + Label ok; + cmpdi(CCR0, count, 0); + bgt(CCR0, ok); + stop("count must be positive"); + bind(ok); + } +#endif + + mtctr(count); + + bind(Lloop); + ld(scratch, 0 , addr); + xor_(scratch, scratch, value); + beq(CCR0, Lexit); + addi(addr, addr, wordSize); + bdnz(Lloop); + + bind(Lexit); +} + +// Ensure that the inline code and the stub are using the same registers. +#define LOOKUP_SECONDARY_SUPERS_TABLE_REGISTERS \ +do { \ + assert(r_super_klass == R4_ARG2 && \ + r_array_base == R3_ARG1 && \ + r_array_length == R7_ARG5 && \ + (r_array_index == R6_ARG4 || r_array_index == noreg) && \ + (r_sub_klass == R5_ARG3 || r_sub_klass == noreg) && \ + (r_bitmap == R11_scratch1 || r_bitmap == noreg) && \ + (result == R8_ARG6 || result == noreg), "registers must match ppc64.ad"); \ +} while(0) + +// Return true: we succeeded in generating this code +void MacroAssembler::lookup_secondary_supers_table(Register r_sub_klass, + Register r_super_klass, + Register temp1, + Register temp2, + Register temp3, + Register temp4, + Register result, + u1 super_klass_slot) { + assert_different_registers(r_sub_klass, r_super_klass, temp1, temp2, temp3, temp4, result); + + Label L_done; + + BLOCK_COMMENT("lookup_secondary_supers_table {"); + + const Register + r_array_base = temp1, + r_array_length = temp2, + r_array_index = temp3, + r_bitmap = temp4; + + LOOKUP_SECONDARY_SUPERS_TABLE_REGISTERS; + + ld(r_bitmap, in_bytes(Klass::bitmap_offset()), r_sub_klass); + + // First check the bitmap to see if super_klass might be present. If + // the bit is zero, we are certain that super_klass is not one of + // the secondary supers. + u1 bit = super_klass_slot; + int shift_count = Klass::SECONDARY_SUPERS_TABLE_MASK - bit; + + // if (shift_count == 0) this is used for comparing with 0: + sldi_(r_array_index, r_bitmap, shift_count); + + li(result, 1); // failure + // We test the MSB of r_array_index, i.e. its sign bit + bge(CCR0, L_done); + + // We will consult the secondary-super array. + ld(r_array_base, in_bytes(Klass::secondary_supers_offset()), r_sub_klass); + + // The value i in r_array_index is >= 1, so even though r_array_base + // points to the length, we don't need to adjust it to point to the + // data. + assert(Array::base_offset_in_bytes() == wordSize, "Adjust this code"); + + // Get the first array index that can contain super_klass. + if (bit != 0) { + popcntd(r_array_index, r_array_index); + // NB! r_array_index is off by 1. It is compensated by keeping r_array_base off by 1 word. + sldi(r_array_index, r_array_index, LogBytesPerWord); // scale + ldx(result, r_array_base, r_array_index); + } else { + // Actually use index 0, but r_array_base and r_array_index are off by 1 word + // such that the sum is precise. + ld(result, BytesPerWord, r_array_base); + li(r_array_index, BytesPerWord); // for slow path (scaled) + } + + xor_(result, result, r_super_klass); + beq(CCR0, L_done); // Found a match (result == 0) + + // Is there another entry to check? Consult the bitmap. + testbitdi(CCR0, /* temp */ r_array_length, r_bitmap, (bit + 1) & Klass::SECONDARY_SUPERS_TABLE_MASK); + beq(CCR0, L_done); // (result != 0) + + // Linear probe. Rotate the bitmap so that the next bit to test is + // in Bit 2 for the look-ahead check in the slow path. + if (bit != 0) { + rldicl(r_bitmap, r_bitmap, 64 - bit, 0); + } + + // Calls into the stub generated by lookup_secondary_supers_table_slow_path. + // Arguments: r_super_klass, r_array_base, r_array_index, r_bitmap. + // Kills: r_array_length. + // Returns: result. + address stub = StubRoutines::lookup_secondary_supers_table_slow_path_stub(); + Register r_stub_addr = r_array_length; + add_const_optimized(r_stub_addr, R29_TOC, MacroAssembler::offset_to_global_toc(stub), R0); + mtctr(r_stub_addr); + bctrl(); + + bind(L_done); + BLOCK_COMMENT("} lookup_secondary_supers_table"); + + if (VerifySecondarySupers) { + verify_secondary_supers_table(r_sub_klass, r_super_klass, result, + temp1, temp2, temp3); + } +} + +// Called by code generated by check_klass_subtype_slow_path +// above. This is called when there is a collision in the hashed +// lookup in the secondary supers array. +void MacroAssembler::lookup_secondary_supers_table_slow_path(Register r_super_klass, + Register r_array_base, + Register r_array_index, + Register r_bitmap, + Register result, + Register temp1) { + assert_different_registers(r_super_klass, r_array_base, r_array_index, r_bitmap, result, temp1); + + const Register + r_array_length = temp1, + r_sub_klass = noreg; + + LOOKUP_SECONDARY_SUPERS_TABLE_REGISTERS; + + Label L_done; + + // Load the array length. + lwa(r_array_length, Array::length_offset_in_bytes(), r_array_base); + // And adjust the array base to point to the data. + // NB! Effectively increments current slot index by 1. + assert(Array::base_offset_in_bytes() == wordSize, ""); + addi(r_array_base, r_array_base, Array::base_offset_in_bytes()); + + // Linear probe + Label L_huge; + + // The bitmap is full to bursting. + // Implicit invariant: BITMAP_FULL implies (length > 0) + assert(Klass::SECONDARY_SUPERS_BITMAP_FULL == ~uintx(0), ""); + cmpdi(CCR0, r_bitmap, -1); + beq(CCR0, L_huge); + + // NB! Our caller has checked bits 0 and 1 in the bitmap. The + // current slot (at secondary_supers[r_array_index]) has not yet + // been inspected, and r_array_index may be out of bounds if we + // wrapped around the end of the array. + + { // This is conventional linear probing, but instead of terminating + // when a null entry is found in the table, we maintain a bitmap + // in which a 0 indicates missing entries. + // The check above guarantees there are 0s in the bitmap, so the loop + // eventually terminates. + +#ifdef ASSERT + { + // We should only reach here after having found a bit in the bitmap. + // Invariant: array_length == popcount(bitmap) + Label ok; + cmpdi(CCR0, r_array_length, 0); + bgt(CCR0, ok); + stop("array_length must be positive"); + bind(ok); + } +#endif + + // Compute limit in r_array_length + addi(r_array_length, r_array_length, -1); + sldi(r_array_length, r_array_length, LogBytesPerWord); + + Label L_loop; + bind(L_loop); + + // Check for wraparound. + cmpd(CCR0, r_array_index, r_array_length); + isel_0(r_array_index, CCR0, Assembler::greater); + + ldx(result, r_array_base, r_array_index); + xor_(result, result, r_super_klass); + beq(CCR0, L_done); // success (result == 0) + + // look-ahead check (Bit 2); result is non-zero + testbitdi(CCR0, R0, r_bitmap, 2); + beq(CCR0, L_done); // fail (result != 0) + + rldicl(r_bitmap, r_bitmap, 64 - 1, 0); + addi(r_array_index, r_array_index, BytesPerWord); + b(L_loop); + } + + { // Degenerate case: more than 64 secondary supers. + // FIXME: We could do something smarter here, maybe a vectorized + // comparison or a binary search, but is that worth any added + // complexity? + bind(L_huge); + repne_scan(r_array_base, r_super_klass, r_array_length, result); + } + + bind(L_done); +} + +// Make sure that the hashed lookup and a linear scan agree. +void MacroAssembler::verify_secondary_supers_table(Register r_sub_klass, + Register r_super_klass, + Register result, + Register temp1, + Register temp2, + Register temp3) { + assert_different_registers(r_sub_klass, r_super_klass, result, temp1, temp2, temp3); + + const Register + r_array_base = temp1, + r_array_length = temp2, + r_array_index = temp3, + r_bitmap = noreg; // unused + + LOOKUP_SECONDARY_SUPERS_TABLE_REGISTERS; + + BLOCK_COMMENT("verify_secondary_supers_table {"); + + Label passed, failure; + + // We will consult the secondary-super array. + ld(r_array_base, in_bytes(Klass::secondary_supers_offset()), r_sub_klass); + // Load the array length. + lwa(r_array_length, Array::length_offset_in_bytes(), r_array_base); + // And adjust the array base to point to the data. + addi(r_array_base, r_array_base, Array::base_offset_in_bytes()); + + // convert !=0 to 1 + neg(R0, result); + orr(result, result, R0); + srdi(result, result, 63); + + const Register linear_result = r_array_index; // reuse + li(linear_result, 1); + cmpdi(CCR0, r_array_length, 0); + ble(CCR0, failure); + repne_scan(r_array_base, r_super_klass, r_array_length, linear_result); + bind(failure); + + // convert !=0 to 1 + neg(R0, linear_result); + orr(linear_result, linear_result, R0); + srdi(linear_result, linear_result, 63); + + cmpd(CCR0, result, linear_result); + beq(CCR0, passed); + + assert_different_registers(R3_ARG1, r_sub_klass, linear_result, result); + mr_if_needed(R3_ARG1, r_super_klass); + assert_different_registers(R4_ARG2, linear_result, result); + mr_if_needed(R4_ARG2, r_sub_klass); + assert_different_registers(R5_ARG3, result); + neg(R5_ARG3, linear_result); + neg(R6_ARG4, result); + const char* msg = "mismatch"; + load_const_optimized(R7_ARG5, (intptr_t)msg, R0); + call_VM_leaf(CAST_FROM_FN_PTR(address, Klass::on_secondary_supers_verification_failure)); + should_not_reach_here(); + + bind(passed); + + BLOCK_COMMENT("} verify_secondary_supers_table"); +} + void MacroAssembler::clinit_barrier(Register klass, Register thread, Label* L_fast_path, Label* L_slow_path) { assert(L_fast_path != nullptr || L_slow_path != nullptr, "at least one is required"); diff --git a/src/hotspot/cpu/ppc/macroAssembler_ppc.hpp b/src/hotspot/cpu/ppc/macroAssembler_ppc.hpp index 4444690e3a3..4f2ff708a46 100644 --- a/src/hotspot/cpu/ppc/macroAssembler_ppc.hpp +++ b/src/hotspot/cpu/ppc/macroAssembler_ppc.hpp @@ -604,6 +604,33 @@ class MacroAssembler: public Assembler { Register temp2_reg, Label& L_success); + void repne_scan(Register addr, Register value, Register count, Register scratch); + + // As above, but with a constant super_klass. + // The result is in Register result, not the condition codes. + void lookup_secondary_supers_table(Register r_sub_klass, + Register r_super_klass, + Register temp1, + Register temp2, + Register temp3, + Register temp4, + Register result, + u1 super_klass_slot); + + void verify_secondary_supers_table(Register r_sub_klass, + Register r_super_klass, + Register result, + Register temp1, + Register temp2, + Register temp3); + + void lookup_secondary_supers_table_slow_path(Register r_super_klass, + Register r_array_base, + Register r_array_index, + Register r_bitmap, + Register result, + Register temp1); + void clinit_barrier(Register klass, Register thread, Label* L_fast_path = nullptr, diff --git a/src/hotspot/cpu/ppc/ppc.ad b/src/hotspot/cpu/ppc/ppc.ad index cbe28deb516..8917b344e54 100644 --- a/src/hotspot/cpu/ppc/ppc.ad +++ b/src/hotspot/cpu/ppc/ppc.ad @@ -641,6 +641,8 @@ reg_class rarg1_bits64_reg(R3_H, R3); reg_class rarg2_bits64_reg(R4_H, R4); reg_class rarg3_bits64_reg(R5_H, R5); reg_class rarg4_bits64_reg(R6_H, R6); +reg_class rarg5_bits64_reg(R7_H, R7); +reg_class rarg6_bits64_reg(R8_H, R8); // Thread register, 'written' by tlsLoadP, see there. reg_class thread_bits64_reg(R16_H, R16); @@ -3430,6 +3432,7 @@ encode %{ call->_in_rms = _in_rms; call->_nesting = _nesting; call->_override_symbolic_info = _override_symbolic_info; + call->_arg_escape = _arg_escape; // New call needs all inputs of old call. // Req... @@ -4353,6 +4356,8 @@ operand iRegPsrc() %{ match(rarg2RegP); match(rarg3RegP); match(rarg4RegP); + match(rarg5RegP); + match(rarg6RegP); match(threadRegP); format %{ %} interface(REG_INTER); @@ -4408,6 +4413,20 @@ operand rarg4RegP() %{ interface(REG_INTER); %} +operand rarg5RegP() %{ + constraint(ALLOC_IN_RC(rarg5_bits64_reg)); + match(iRegPdst); + format %{ %} + interface(REG_INTER); +%} + +operand rarg6RegP() %{ + constraint(ALLOC_IN_RC(rarg6_bits64_reg)); + match(iRegPdst); + format %{ %} + interface(REG_INTER); +%} + operand iRegNsrc() %{ constraint(ALLOC_IN_RC(bits32_reg_ro)); match(RegN); @@ -12023,6 +12042,35 @@ instruct partialSubtypeCheck(iRegPdst result, iRegP_N2P subklass, iRegP_N2P supe ins_pipe(pipe_class_default); %} +instruct partialSubtypeCheckConstSuper(rarg3RegP sub, rarg2RegP super_reg, immP super_con, rarg6RegP result, + rarg1RegP tempR1, rarg5RegP tempR2, rarg4RegP tempR3, rscratch1RegP tempR4, + flagsRegCR0 cr0, regCTR ctr) +%{ + match(Set result (PartialSubtypeCheck sub (Binary super_reg super_con))); + predicate(UseSecondarySupersTable); + effect(KILL cr0, KILL ctr, TEMP tempR1, TEMP tempR2, TEMP tempR3, TEMP tempR4); + + ins_cost(DEFAULT_COST*8); // smaller than the other version + format %{ "partialSubtypeCheck $result, $sub, $super_reg" %} + + ins_encode %{ + u1 super_klass_slot = ((Klass*)$super_con$$constant)->hash_slot(); + if (InlineSecondarySupersTest) { + __ lookup_secondary_supers_table($sub$$Register, $super_reg$$Register, + $tempR1$$Register, $tempR2$$Register, $tempR3$$Register, $tempR4$$Register, + $result$$Register, super_klass_slot); + } else { + address stub = StubRoutines::lookup_secondary_supers_table_stub(super_klass_slot); + Register r_stub_addr = $tempR1$$Register; + __ add_const_optimized(r_stub_addr, R29_TOC, MacroAssembler::offset_to_global_toc(stub), R0); + __ mtctr(r_stub_addr); + __ bctrl(); + } + %} + + ins_pipe(pipe_class_memory); +%} + // inlined locking and unlocking instruct cmpFastLock(flagsRegCR0 crx, iRegPdst oop, iRegPdst box, iRegPdst tmp1, iRegPdst tmp2) %{ diff --git a/src/hotspot/cpu/ppc/stubGenerator_ppc.cpp b/src/hotspot/cpu/ppc/stubGenerator_ppc.cpp index ee7aa27a630..8da7cf7e791 100644 --- a/src/hotspot/cpu/ppc/stubGenerator_ppc.cpp +++ b/src/hotspot/cpu/ppc/stubGenerator_ppc.cpp @@ -4531,6 +4531,46 @@ class StubGenerator: public StubCodeGenerator { #endif // VM_LITTLE_ENDIAN +address generate_lookup_secondary_supers_table_stub(u1 super_klass_index) { + StubCodeMark mark(this, "StubRoutines", "lookup_secondary_supers_table"); + + address start = __ pc(); + const Register + r_super_klass = R4_ARG2, + r_array_base = R3_ARG1, + r_array_length = R7_ARG5, + r_array_index = R6_ARG4, + r_sub_klass = R5_ARG3, + r_bitmap = R11_scratch1, + result = R8_ARG6; + + __ lookup_secondary_supers_table(r_sub_klass, r_super_klass, + r_array_base, r_array_length, r_array_index, + r_bitmap, result, super_klass_index); + __ blr(); + + return start; + } + + // Slow path implementation for UseSecondarySupersTable. + address generate_lookup_secondary_supers_table_slow_path_stub() { + StubCodeMark mark(this, "StubRoutines", "lookup_secondary_supers_table_slow_path"); + + address start = __ pc(); + const Register + r_super_klass = R4_ARG2, + r_array_base = R3_ARG1, + temp1 = R7_ARG5, + r_array_index = R6_ARG4, + r_bitmap = R11_scratch1, + result = R8_ARG6; + + __ lookup_secondary_supers_table_slow_path(r_super_klass, r_array_base, r_array_index, r_bitmap, result, temp1); + __ blr(); + + return start; + } + address generate_cont_thaw(const char* label, Continuation::thaw_kind kind) { if (!Continuations::enabled()) return nullptr; @@ -4807,6 +4847,16 @@ class StubGenerator: public StubCodeGenerator { // arraycopy stubs used by compilers generate_arraycopy_stubs(); + if (UseSecondarySupersTable) { + StubRoutines::_lookup_secondary_supers_table_slow_path_stub = generate_lookup_secondary_supers_table_slow_path_stub(); + if (!InlineSecondarySupersTest) { + for (int slot = 0; slot < Klass::SECONDARY_SUPERS_TABLE_SIZE; slot++) { + StubRoutines::_lookup_secondary_supers_table_stubs[slot] + = generate_lookup_secondary_supers_table_stub(slot); + } + } + } + StubRoutines::_upcall_stub_exception_handler = generate_upcall_stub_exception_handler(); } diff --git a/src/hotspot/cpu/ppc/vm_version_ppc.cpp b/src/hotspot/cpu/ppc/vm_version_ppc.cpp index 5a9d035be60..20578ed3b8b 100644 --- a/src/hotspot/cpu/ppc/vm_version_ppc.cpp +++ b/src/hotspot/cpu/ppc/vm_version_ppc.cpp @@ -340,6 +340,13 @@ void VM_Version::initialize() { FLAG_SET_DEFAULT(UseSHA, false); } + if (UseSecondarySupersTable && PowerArchitecturePPC64 < 7) { + if (!FLAG_IS_DEFAULT(UseSecondarySupersTable)) { + warning("UseSecondarySupersTable requires Power7 or later."); + } + FLAG_SET_DEFAULT(UseSecondarySupersTable, false); + } + #ifdef COMPILER2 if (FLAG_IS_DEFAULT(UseSquareToLenIntrinsic)) { UseSquareToLenIntrinsic = true; diff --git a/src/hotspot/cpu/ppc/vm_version_ppc.hpp b/src/hotspot/cpu/ppc/vm_version_ppc.hpp index 0efde131277..9d8e4b88ee2 100644 --- a/src/hotspot/cpu/ppc/vm_version_ppc.hpp +++ b/src/hotspot/cpu/ppc/vm_version_ppc.hpp @@ -95,6 +95,7 @@ class VM_Version: public Abstract_VM_Version { static bool supports_fast_class_init_checks() { return true; } constexpr static bool supports_stack_watermark_barrier() { return true; } constexpr static bool supports_recursive_lightweight_locking() { return true; } + constexpr static bool supports_secondary_supers_table() { return true; } static bool is_determine_features_test_running() { return _is_determine_features_test_running; } // CPU instruction support diff --git a/src/hotspot/cpu/riscv/riscv_v.ad b/src/hotspot/cpu/riscv/riscv_v.ad index 4a71da9f20c..841e8cb260c 100644 --- a/src/hotspot/cpu/riscv/riscv_v.ad +++ b/src/hotspot/cpu/riscv/riscv_v.ad @@ -2007,11 +2007,20 @@ instruct reduce_addL(iRegLNoSp dst, iRegL src1, vReg src2, vReg tmp) %{ ins_pipe(pipe_slow); %} -instruct reduce_addF(fRegF dst, fRegF src1, vReg src2, vReg tmp) %{ +// Distinguish two cases based on requires_strict_order +// 1. Non strictly-ordered AddReductionVF/D. For example, AddReductionVF/D +// generated by Vector API. It is more beneficial performance-wise to do +// an unordered FP reduction sum (vfredusum.vs). +// 2. Strictly-ordered AddReductionVF/D. For example, AddReductionVF/D +// generated by auto-vectorization. Must do an ordered FP reduction sum +// (vfredosum.vs). + +instruct reduce_addF_ordered(fRegF dst, fRegF src1, vReg src2, vReg tmp) %{ + predicate(n->as_Reduction()->requires_strict_order()); match(Set dst (AddReductionVF src1 src2)); effect(TEMP tmp); ins_cost(VEC_COST); - format %{ "reduce_addF $dst, $src1, $src2\t# KILL $tmp" %} + format %{ "reduce_addF_ordered $dst, $src1, $src2\t# KILL $tmp" %} ins_encode %{ __ vsetvli_helper(T_FLOAT, Matcher::vector_length(this, $src2)); __ vfmv_s_f(as_VectorRegister($tmp$$reg), $src1$$FloatRegister); @@ -2022,11 +2031,28 @@ instruct reduce_addF(fRegF dst, fRegF src1, vReg src2, vReg tmp) %{ ins_pipe(pipe_slow); %} -instruct reduce_addD(fRegD dst, fRegD src1, vReg src2, vReg tmp) %{ +instruct reduce_addF_unordered(fRegF dst, fRegF src1, vReg src2, vReg tmp) %{ + predicate(!n->as_Reduction()->requires_strict_order()); + match(Set dst (AddReductionVF src1 src2)); + effect(TEMP tmp); + ins_cost(VEC_COST); + format %{ "reduce_addF_unordered $dst, $src1, $src2\t# KILL $tmp" %} + ins_encode %{ + __ vsetvli_helper(T_FLOAT, Matcher::vector_length(this, $src2)); + __ vfmv_s_f(as_VectorRegister($tmp$$reg), $src1$$FloatRegister); + __ vfredusum_vs(as_VectorRegister($tmp$$reg), as_VectorRegister($src2$$reg), + as_VectorRegister($tmp$$reg)); + __ vfmv_f_s($dst$$FloatRegister, as_VectorRegister($tmp$$reg)); + %} + ins_pipe(pipe_slow); +%} + +instruct reduce_addD_ordered(fRegD dst, fRegD src1, vReg src2, vReg tmp) %{ + predicate(n->as_Reduction()->requires_strict_order()); match(Set dst (AddReductionVD src1 src2)); effect(TEMP tmp); ins_cost(VEC_COST); - format %{ "reduce_addD $dst, $src1, $src2\t# KILL $tmp" %} + format %{ "reduce_addD_ordered $dst, $src1, $src2\t# KILL $tmp" %} ins_encode %{ __ vsetvli_helper(T_DOUBLE, Matcher::vector_length(this, $src2)); __ vfmv_s_f(as_VectorRegister($tmp$$reg), $src1$$FloatRegister); @@ -2037,6 +2063,22 @@ instruct reduce_addD(fRegD dst, fRegD src1, vReg src2, vReg tmp) %{ ins_pipe(pipe_slow); %} +instruct reduce_addD_unordered(fRegD dst, fRegD src1, vReg src2, vReg tmp) %{ + predicate(!n->as_Reduction()->requires_strict_order()); + match(Set dst (AddReductionVD src1 src2)); + effect(TEMP tmp); + ins_cost(VEC_COST); + format %{ "reduce_addD_unordered $dst, $src1, $src2\t# KILL $tmp" %} + ins_encode %{ + __ vsetvli_helper(T_DOUBLE, Matcher::vector_length(this, $src2)); + __ vfmv_s_f(as_VectorRegister($tmp$$reg), $src1$$FloatRegister); + __ vfredusum_vs(as_VectorRegister($tmp$$reg), as_VectorRegister($src2$$reg), + as_VectorRegister($tmp$$reg)); + __ vfmv_f_s($dst$$FloatRegister, as_VectorRegister($tmp$$reg)); + %} + ins_pipe(pipe_slow); +%} + // vector add reduction - predicated instruct reduce_addI_masked(iRegINoSp dst, iRegIorL2I src1, vReg src2, vRegMask_V0 v0, vReg tmp) %{ diff --git a/src/hotspot/cpu/x86/nativeInst_x86.cpp b/src/hotspot/cpu/x86/nativeInst_x86.cpp index b59f4246256..0426c0f5174 100644 --- a/src/hotspot/cpu/x86/nativeInst_x86.cpp +++ b/src/hotspot/cpu/x86/nativeInst_x86.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 @@ -41,143 +41,6 @@ void NativeInstruction::wrote(int offset) { ICache::invalidate_word(addr_at(offset)); } -#ifdef ASSERT -void NativeLoadGot::report_and_fail() const { - tty->print_cr("Addr: " INTPTR_FORMAT " Code: %x %x %x", p2i(instruction_address()), - (has_rex ? ubyte_at(0) : 0), ubyte_at(rex_size), ubyte_at(rex_size + 1)); - fatal("not a indirect rip mov to rbx"); -} - -void NativeLoadGot::verify() const { - if (has_rex) { - int rex = ubyte_at(0); - if (rex != rex_prefix && rex != rex_b_prefix) { - report_and_fail(); - } - } - - int inst = ubyte_at(rex_size); - if (inst != instruction_code) { - report_and_fail(); - } - int modrm = ubyte_at(rex_size + 1); - if (modrm != modrm_rbx_code && modrm != modrm_rax_code) { - report_and_fail(); - } -} -#endif - -intptr_t NativeLoadGot::data() const { - return *(intptr_t *) got_address(); -} - -address NativePltCall::destination() const { - NativeGotJump* jump = nativeGotJump_at(plt_jump()); - return jump->destination(); -} - -address NativePltCall::plt_entry() const { - return return_address() + displacement(); -} - -address NativePltCall::plt_jump() const { - address entry = plt_entry(); - // Virtual PLT code has move instruction first - if (((NativeGotJump*)entry)->is_GotJump()) { - return entry; - } else { - return nativeLoadGot_at(entry)->next_instruction_address(); - } -} - -address NativePltCall::plt_load_got() const { - address entry = plt_entry(); - if (!((NativeGotJump*)entry)->is_GotJump()) { - // Virtual PLT code has move instruction first - return entry; - } else { - // Static PLT code has move instruction second (from c2i stub) - return nativeGotJump_at(entry)->next_instruction_address(); - } -} - -address NativePltCall::plt_c2i_stub() const { - address entry = plt_load_got(); - // This method should be called only for static calls which has C2I stub. - NativeLoadGot* load = nativeLoadGot_at(entry); - return entry; -} - -address NativePltCall::plt_resolve_call() const { - NativeGotJump* jump = nativeGotJump_at(plt_jump()); - address entry = jump->next_instruction_address(); - if (((NativeGotJump*)entry)->is_GotJump()) { - return entry; - } else { - // c2i stub 2 instructions - entry = nativeLoadGot_at(entry)->next_instruction_address(); - return nativeGotJump_at(entry)->next_instruction_address(); - } -} - -void NativePltCall::reset_to_plt_resolve_call() { - set_destination_mt_safe(plt_resolve_call()); -} - -void NativePltCall::set_destination_mt_safe(address dest) { - // rewriting the value in the GOT, it should always be aligned - NativeGotJump* jump = nativeGotJump_at(plt_jump()); - address* got = (address *) jump->got_address(); - *got = dest; -} - -void NativePltCall::set_stub_to_clean() { - NativeLoadGot* method_loader = nativeLoadGot_at(plt_c2i_stub()); - NativeGotJump* jump = nativeGotJump_at(method_loader->next_instruction_address()); - method_loader->set_data(0); - jump->set_jump_destination((address)-1); -} - -void NativePltCall::verify() const { - // Make sure code pattern is actually a call rip+off32 instruction. - int inst = ubyte_at(0); - if (inst != instruction_code) { - tty->print_cr("Addr: " INTPTR_FORMAT " Code: 0x%x", p2i(instruction_address()), - inst); - fatal("not a call rip+off32"); - } -} - -address NativeGotJump::destination() const { - address *got_entry = (address *) got_address(); - return *got_entry; -} - -#ifdef ASSERT -void NativeGotJump::report_and_fail() const { - tty->print_cr("Addr: " INTPTR_FORMAT " Code: %x %x %x", p2i(instruction_address()), - (has_rex() ? ubyte_at(0) : 0), ubyte_at(rex_size()), ubyte_at(rex_size() + 1)); - fatal("not a indirect rip jump"); -} - -void NativeGotJump::verify() const { - if (has_rex()) { - int rex = ubyte_at(0); - if (rex != rex_prefix) { - report_and_fail(); - } - } - int inst = ubyte_at(rex_size()); - if (inst != instruction_code) { - report_and_fail(); - } - int modrm = ubyte_at(rex_size() + 1); - if (modrm != modrm_code) { - report_and_fail(); - } -} -#endif - void NativeCall::verify() { // Make sure code pattern is actually a call imm32 instruction. int inst = ubyte_at(0); @@ -565,28 +428,6 @@ void NativeJump::patch_verified_entry(address entry, address verified_entry, add } -address NativeFarJump::jump_destination() const { - NativeMovConstReg* mov = nativeMovConstReg_at(addr_at(0)); - return (address)mov->data(); -} - -void NativeFarJump::verify() { - if (is_far_jump()) { - NativeMovConstReg* mov = nativeMovConstReg_at(addr_at(0)); - NativeInstruction* jmp = nativeInstruction_at(mov->next_instruction_address()); - if (jmp->is_jump_reg()) return; - } - fatal("not a jump instruction"); -} - -void NativePopReg::insert(address code_pos, Register reg) { - assert(reg->encoding() < 8, "no space for REX"); - assert(NativePopReg::instruction_size == sizeof(char), "right address unit for update"); - *code_pos = (u_char)(instruction_code | reg->encoding()); - ICache::invalidate_range(code_pos, instruction_size); -} - - void NativeIllegalInstruction::insert(address code_pos) { assert(NativeIllegalInstruction::instruction_size == sizeof(short), "right address unit for update"); *(short *)code_pos = instruction_code; diff --git a/src/hotspot/cpu/x86/nativeInst_x86.hpp b/src/hotspot/cpu/x86/nativeInst_x86.hpp index 70cb6179366..3a300472944 100644 --- a/src/hotspot/cpu/x86/nativeInst_x86.hpp +++ b/src/hotspot/cpu/x86/nativeInst_x86.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 @@ -37,7 +37,6 @@ // - - NativeMovRegMem // - - NativeMovRegMemPatching // - - NativeJump -// - - NativeFarJump // - - NativeIllegalOpCode // - - NativeGeneralJump // - - NativeReturn @@ -64,7 +63,6 @@ class NativeInstruction { inline bool is_return(); inline bool is_jump(); inline bool is_jump_reg(); - inline bool is_far_jump(); inline bool is_cond_jump(); inline bool is_safepoint_poll(); inline bool is_mov_literal64(); @@ -104,47 +102,6 @@ inline NativeInstruction* nativeInstruction_at(address address) { return inst; } -class NativePltCall: public NativeInstruction { -public: - enum Intel_specific_constants { - instruction_code = 0xE8, - instruction_size = 5, - instruction_offset = 0, - displacement_offset = 1, - return_address_offset = 5 - }; - address instruction_address() const { return addr_at(instruction_offset); } - address next_instruction_address() const { return addr_at(return_address_offset); } - address displacement_address() const { return addr_at(displacement_offset); } - int displacement() const { return (jint) int_at(displacement_offset); } - address return_address() const { return addr_at(return_address_offset); } - address destination() const; - address plt_entry() const; - address plt_jump() const; - address plt_load_got() const; - address plt_resolve_call() const; - address plt_c2i_stub() const; - void set_stub_to_clean(); - - void reset_to_plt_resolve_call(); - void set_destination_mt_safe(address dest); - - void verify() const; -}; - -inline NativePltCall* nativePltCall_at(address address) { - NativePltCall* call = (NativePltCall*) address; -#ifdef ASSERT - call->verify(); -#endif - return call; -} - -inline NativePltCall* nativePltCall_before(address addr) { - address at = addr - NativePltCall::instruction_size; - return nativePltCall_at(at); -} - class NativeCall; inline NativeCall* nativeCall_at(address address); // The NativeCall is an abstraction for accessing/manipulating native call imm32/rel32off @@ -426,57 +383,6 @@ class NativeLoadAddress: public NativeMovRegMem { } }; -// destination is rbx or rax -// mov rbx, [rip + offset] -class NativeLoadGot: public NativeInstruction { -#ifdef AMD64 - static const bool has_rex = true; - static const int rex_size = 1; -#else - static const bool has_rex = false; - static const int rex_size = 0; -#endif - - enum Intel_specific_constants { - rex_prefix = 0x48, - rex_b_prefix = 0x49, - instruction_code = 0x8b, - modrm_rbx_code = 0x1d, - modrm_rax_code = 0x05, - instruction_length = 6 + rex_size, - offset_offset = 2 + rex_size - }; - - int rip_offset() const { return int_at(offset_offset); } - address return_address() const { return addr_at(instruction_length); } - address got_address() const { return return_address() + rip_offset(); } - -#ifdef ASSERT - void report_and_fail() const; - address instruction_address() const { return addr_at(0); } -#endif - -public: - address next_instruction_address() const { return return_address(); } - intptr_t data() const; - void set_data(intptr_t data) { - intptr_t *addr = (intptr_t *) got_address(); - *addr = data; - } - - DEBUG_ONLY( void verify() const ); -}; - -inline NativeLoadGot* nativeLoadGot_at(address addr) { - NativeLoadGot* load = (NativeLoadGot*) addr; -#ifdef ASSERT - load->verify(); -#endif - return load; -} - -// jump rel32off - class NativeJump: public NativeInstruction { public: enum Intel_specific_constants { @@ -532,26 +438,6 @@ inline NativeJump* nativeJump_at(address address) { return jump; } -// far jump reg -class NativeFarJump: public NativeInstruction { - public: - address jump_destination() const; - - // Creation - inline friend NativeFarJump* nativeFarJump_at(address address); - - void verify(); - -}; - -inline NativeFarJump* nativeFarJump_at(address address) { - NativeFarJump* jump = (NativeFarJump*)(address); -#ifdef ASSERT - jump->verify(); -#endif - return jump; -} - // Handles all kinds of jump on Intel. Long/far, conditional/unconditional class NativeGeneralJump: public NativeInstruction { public: @@ -585,61 +471,6 @@ inline NativeGeneralJump* nativeGeneralJump_at(address address) { return jump; } -class NativeGotJump: public NativeInstruction { - enum Intel_specific_constants { - rex_prefix = 0x41, - instruction_code = 0xff, - modrm_code = 0x25, - instruction_size = 6, - rip_offset = 2 - }; - - bool has_rex() const { return ubyte_at(0) == rex_prefix; } - int rex_size() const { return has_rex() ? 1 : 0; } - - address return_address() const { return addr_at(instruction_size + rex_size()); } - int got_offset() const { return (jint) int_at(rip_offset + rex_size()); } - -#ifdef ASSERT - void report_and_fail() const; - address instruction_address() const { return addr_at(0); } -#endif - -public: - address got_address() const { return return_address() + got_offset(); } - address next_instruction_address() const { return return_address(); } - bool is_GotJump() const { return ubyte_at(rex_size()) == instruction_code; } - - address destination() const; - void set_jump_destination(address dest) { - address *got_entry = (address *) got_address(); - *got_entry = dest; - } - - DEBUG_ONLY( void verify() const; ) -}; - -inline NativeGotJump* nativeGotJump_at(address addr) { - NativeGotJump* jump = (NativeGotJump*)(addr); - debug_only(jump->verify()); - return jump; -} - -class NativePopReg : public NativeInstruction { - public: - enum Intel_specific_constants { - instruction_code = 0x58, - instruction_size = 1, - instruction_offset = 0, - data_offset = 1, - next_instruction_offset = 1 - }; - - // Insert a pop instruction - static void insert(address code_pos, Register reg); -}; - - class NativeIllegalInstruction: public NativeInstruction { public: enum Intel_specific_constants { @@ -702,7 +533,6 @@ inline bool NativeInstruction::is_jump_reg() { if (ubyte_at(0) == Assembler::REX_B) pos = 1; return ubyte_at(pos) == 0xFF && (ubyte_at(pos + 1) & 0xF0) == 0xE0; } -inline bool NativeInstruction::is_far_jump() { return is_mov_literal64(); } inline bool NativeInstruction::is_cond_jump() { return (int_at(0) & 0xF0FF) == 0x800F /* long jump */ || (ubyte_at(0) & 0xF0) == 0x70; /* short jump */ } inline bool NativeInstruction::is_safepoint_poll() { diff --git a/src/hotspot/os/aix/os_aix.cpp b/src/hotspot/os/aix/os_aix.cpp index f0a984d3d1f..39f3e420662 100644 --- a/src/hotspot/os/aix/os_aix.cpp +++ b/src/hotspot/os/aix/os_aix.cpp @@ -287,6 +287,8 @@ julong os::physical_memory() { return Aix::physical_memory(); } +size_t os::rss() { return (size_t)0; } + // Cpu architecture string #if defined(PPC32) static char cpu_arch[] = "ppc"; diff --git a/src/hotspot/os/bsd/os_bsd.cpp b/src/hotspot/os/bsd/os_bsd.cpp index e5b6c74ce2f..fe1e7098fd5 100644 --- a/src/hotspot/os/bsd/os_bsd.cpp +++ b/src/hotspot/os/bsd/os_bsd.cpp @@ -210,6 +210,22 @@ julong os::physical_memory() { return Bsd::physical_memory(); } +size_t os::rss() { + size_t rss = 0; +#ifdef __APPLE__ + mach_task_basic_info info; + mach_msg_type_number_t count = MACH_TASK_BASIC_INFO_COUNT; + + kern_return_t ret = task_info(mach_task_self(), MACH_TASK_BASIC_INFO, + (task_info_t)&info, &count); + if (ret == KERN_SUCCESS) { + rss = info.resident_size; + } +#endif // __APPLE__ + + return rss; +} + // Cpu architecture string #if defined(ZERO) static char cpu_arch[] = ZERO_LIBARCH; diff --git a/src/hotspot/os/linux/cgroupSubsystem_linux.cpp b/src/hotspot/os/linux/cgroupSubsystem_linux.cpp index be9ff1440c0..560589c3602 100644 --- a/src/hotspot/os/linux/cgroupSubsystem_linux.cpp +++ b/src/hotspot/os/linux/cgroupSubsystem_linux.cpp @@ -661,7 +661,7 @@ bool CgroupController::read_numerical_key_value(const char* filename, const char for (; line != nullptr; line = fgets(buf, buf_len, fp)) { char after_key = line[key_len]; if (strncmp(line, key, key_len) == 0 - && isspace(after_key) != 0 + && isspace((unsigned char) after_key) != 0 && after_key != '\n') { // Skip key, skip space const char* value_substr = line + key_len + 1; diff --git a/src/hotspot/os/linux/globals_linux.hpp b/src/hotspot/os/linux/globals_linux.hpp index 607df84a4bf..e09b44a06ee 100644 --- a/src/hotspot/os/linux/globals_linux.hpp +++ b/src/hotspot/os/linux/globals_linux.hpp @@ -132,7 +132,9 @@ product(bool, UseMadvPopulateWrite, true, DIAGNOSTIC, \ "Use MADV_POPULATE_WRITE in os::pd_pretouch_memory.") \ \ - + product(bool, PrintMemoryMapAtExit, false, DIAGNOSTIC, \ + "Print an annotated memory map at exit") \ + \ // end of RUNTIME_OS_FLAGS // diff --git a/src/hotspot/os/linux/os_linux.cpp b/src/hotspot/os/linux/os_linux.cpp index 85acb98e42d..0f0d763ddc6 100644 --- a/src/hotspot/os/linux/os_linux.cpp +++ b/src/hotspot/os/linux/os_linux.cpp @@ -362,6 +362,15 @@ julong os::physical_memory() { return phys_mem; } +size_t os::rss() { + size_t size = 0; + os::Linux::meminfo_t info; + if (os::Linux::query_process_memory_info(&info)) { + size = info.vmrss * K; + } + return size; +} + static uint64_t initial_total_ticks = 0; static uint64_t initial_steal_ticks = 0; static bool has_initial_tick_info = false; @@ -1359,7 +1368,7 @@ void os::Linux::capture_initial_stack(size_t max_size) { i = 0; if (s) { // Skip blank chars - do { s++; } while (s && isspace(*s)); + do { s++; } while (s && isspace((unsigned char) *s)); #define _UFM UINTX_FORMAT #define _DFM INTX_FORMAT @@ -5324,7 +5333,7 @@ static jlong slow_thread_cpu_time(Thread *thread, bool user_sys_cpu_time) { if (s == nullptr) return -1; // Skip blank chars - do { s++; } while (s && isspace(*s)); + do { s++; } while (s && isspace((unsigned char) *s)); count = sscanf(s,"%c %d %d %d %d %d %lu %lu %lu %lu %lu %lu %lu", &cdummy, &idummy, &idummy, &idummy, &idummy, &idummy, diff --git a/src/hotspot/os/posix/malloctrace/mallocTracePosix.cpp b/src/hotspot/os/posix/malloctrace/mallocTracePosix.cpp index b9a05c51aac..c3167c3944b 100644 --- a/src/hotspot/os/posix/malloctrace/mallocTracePosix.cpp +++ b/src/hotspot/os/posix/malloctrace/mallocTracePosix.cpp @@ -268,7 +268,7 @@ void Allocator::free(void* ptr) { } size_t Allocator::allocated() { - return _allocation_size * _entries_per_chunk * _nr_of_chunks; + return _allocation_size * _entries_per_chunk * _nr_of_chunks; } size_t Allocator::unused() { diff --git a/src/hotspot/os/windows/os_windows.cpp b/src/hotspot/os/windows/os_windows.cpp index 773bda647e1..49f05fe94b3 100644 --- a/src/hotspot/os/windows/os_windows.cpp +++ b/src/hotspot/os/windows/os_windows.cpp @@ -858,6 +858,19 @@ julong os::physical_memory() { return win32::physical_memory(); } +size_t os::rss() { + size_t rss = 0; + PROCESS_MEMORY_COUNTERS_EX pmex; + ZeroMemory(&pmex, sizeof(PROCESS_MEMORY_COUNTERS_EX)); + pmex.cb = sizeof(pmex); + BOOL ret = GetProcessMemoryInfo( + GetCurrentProcess(), (PROCESS_MEMORY_COUNTERS *)&pmex, sizeof(pmex)); + if (ret) { + rss = pmex.WorkingSetSize; + } + return rss; +} + bool os::has_allocatable_memory_limit(size_t* limit) { MEMORYSTATUSEX ms; ms.dwLength = sizeof(ms); diff --git a/src/hotspot/os_cpu/linux_riscv/vm_version_linux_riscv.cpp b/src/hotspot/os_cpu/linux_riscv/vm_version_linux_riscv.cpp index f74ed8c8f81..3f9f26b525b 100644 --- a/src/hotspot/os_cpu/linux_riscv/vm_version_linux_riscv.cpp +++ b/src/hotspot/os_cpu/linux_riscv/vm_version_linux_riscv.cpp @@ -131,7 +131,10 @@ void VM_Version::setup_cpu_available_features() { if (_feature_list[i]->feature_string()) { const char* tmp = _feature_list[i]->pretty(); if (strlen(tmp) == 1) { - strcat(buf, " "); + // Feature string is expected to be in multi-character form + // like rvc, rvv, etc so that it will be easier to specify + // target feature string in tests. + strcat(buf, " rv"); strcat(buf, tmp); } else { // Feature string is expected to be lower case. diff --git a/src/hotspot/share/cds/archiveBuilder.cpp b/src/hotspot/share/cds/archiveBuilder.cpp index a98fd04ba68..72e45a7998c 100644 --- a/src/hotspot/share/cds/archiveBuilder.cpp +++ b/src/hotspot/share/cds/archiveBuilder.cpp @@ -717,6 +717,14 @@ void ArchiveBuilder::write_pointer_in_buffer(address* ptr_location, address src_ } } +void ArchiveBuilder::mark_and_relocate_to_buffered_addr(address* ptr_location) { + assert(*ptr_location != nullptr, "sanity"); + if (!is_in_mapped_static_archive(*ptr_location)) { + *ptr_location = get_buffered_addr(*ptr_location); + } + ArchivePtrMarker::mark_pointer(ptr_location); +} + address ArchiveBuilder::get_buffered_addr(address src_addr) const { SourceObjInfo* p = _src_obj_table.get(src_addr); assert(p != nullptr, "src_addr " INTPTR_FORMAT " is used but has not been archived", @@ -755,6 +763,16 @@ void ArchiveBuilder::make_klasses_shareable() { int num_obj_array_klasses = 0; int num_type_array_klasses = 0; + for (int i = 0; i < klasses()->length(); i++) { + // Some of the code in ConstantPool::remove_unshareable_info() requires the classes + // to be in linked state, so it must be call here before the next loop, which returns + // all classes to unlinked state. + Klass* k = get_buffered_addr(klasses()->at(i)); + if (k->is_instance_klass()) { + InstanceKlass::cast(k)->constants()->remove_unshareable_info(); + } + } + for (int i = 0; i < klasses()->length(); i++) { const char* type; const char* unlinked = ""; diff --git a/src/hotspot/share/cds/archiveBuilder.hpp b/src/hotspot/share/cds/archiveBuilder.hpp index cbde5a7e02c..ad0302137f5 100644 --- a/src/hotspot/share/cds/archiveBuilder.hpp +++ b/src/hotspot/share/cds/archiveBuilder.hpp @@ -408,6 +408,11 @@ class ArchiveBuilder : public StackObj { write_pointer_in_buffer((address*)ptr_location, (address)src_addr); } + void mark_and_relocate_to_buffered_addr(address* ptr_location); + template void mark_and_relocate_to_buffered_addr(T ptr_location) { + mark_and_relocate_to_buffered_addr((address*)ptr_location); + } + address get_buffered_addr(address src_addr) const; template T get_buffered_addr(T src_addr) const { return (T)get_buffered_addr((address)src_addr); diff --git a/src/hotspot/share/cds/archiveHeapLoader.cpp b/src/hotspot/share/cds/archiveHeapLoader.cpp index 2ef502a3643..feaf245d22c 100644 --- a/src/hotspot/share/cds/archiveHeapLoader.cpp +++ b/src/hotspot/share/cds/archiveHeapLoader.cpp @@ -34,6 +34,7 @@ #include "memory/iterator.inline.hpp" #include "memory/resourceArea.hpp" #include "memory/universe.hpp" +#include "sanitizers/ub.hpp" #include "utilities/bitMap.inline.hpp" #include "utilities/copy.hpp" @@ -61,6 +62,7 @@ ptrdiff_t ArchiveHeapLoader::_mapped_heap_delta = 0; // Every mapped region is offset by _mapped_heap_delta from its requested address. // See FileMapInfo::heap_region_requested_address(). +ATTRIBUTE_NO_UBSAN void ArchiveHeapLoader::init_mapped_heap_info(address mapped_heap_bottom, ptrdiff_t delta, int dumptime_oop_shift) { assert(!_mapped_heap_relocation_initialized, "only once"); if (!UseCompressedOops) { diff --git a/src/hotspot/share/cds/archiveUtils.cpp b/src/hotspot/share/cds/archiveUtils.cpp index 8fd20e20267..76cfa441fa7 100644 --- a/src/hotspot/share/cds/archiveUtils.cpp +++ b/src/hotspot/share/cds/archiveUtils.cpp @@ -357,7 +357,7 @@ void ArchiveUtils::log_to_classlist(BootstrapInfo* bootstrap_specifier, TRAPS) { ResourceMark rm(THREAD); int pool_index = bootstrap_specifier->bss_index(); ClassListWriter w; - w.stream()->print("%s %s", LAMBDA_PROXY_TAG, pool->pool_holder()->name()->as_C_string()); + w.stream()->print("%s %s", ClassListParser::lambda_proxy_tag(), pool->pool_holder()->name()->as_C_string()); CDSIndyInfo cii; ClassListParser::populate_cds_indy_info(pool, pool_index, &cii, CHECK); GrowableArray* indy_items = cii.items(); diff --git a/src/hotspot/share/cds/classListParser.cpp b/src/hotspot/share/cds/classListParser.cpp index 4455dd7db1b..9ee5f25aa89 100644 --- a/src/hotspot/share/cds/classListParser.cpp +++ b/src/hotspot/share/cds/classListParser.cpp @@ -25,6 +25,7 @@ #include "precompiled.hpp" #include "cds/archiveUtils.hpp" #include "cds/classListParser.hpp" +#include "cds/classPrelinker.hpp" #include "cds/lambdaFormInvokers.hpp" #include "cds/metaspaceShared.hpp" #include "cds/unregisteredClasses.hpp" @@ -52,6 +53,10 @@ #include "utilities/macros.hpp" #include "utilities/utf8.hpp" +const char* ClassListParser::CONSTANT_POOL_TAG = "@cp"; +const char* ClassListParser::LAMBDA_FORM_TAG = "@lambda-form-invoker"; +const char* ClassListParser::LAMBDA_PROXY_TAG = "@lambda-proxy"; + volatile Thread* ClassListParser::_parsing_thread = nullptr; ClassListParser* ClassListParser::_instance = nullptr; @@ -299,6 +304,9 @@ void ClassListParser::parse_at_tags(TRAPS) { } } else if (strcmp(_token, LAMBDA_FORM_TAG) == 0) { LambdaFormInvokers::append(os::strdup((const char*)(_line + offset), mtInternal)); + } else if (strcmp(_token, CONSTANT_POOL_TAG) == 0) { + _token = _line + offset; + parse_constant_pool_tag(); } else { error("Invalid @ tag at the beginning of line \"%s\" line #%zu", _token, lineno()); } @@ -395,9 +403,14 @@ void ClassListParser::print_actual_interfaces(InstanceKlass* ik) { jio_fprintf(defaultStream::error_stream(), "}\n"); } -void ClassListParser::error(const char* msg, ...) { +void ClassListParser::print_diagnostic_info(outputStream* st, const char* msg, ...) { va_list ap; va_start(ap, msg); + print_diagnostic_info(st, msg, ap); + va_end(ap); +} + +void ClassListParser::print_diagnostic_info(outputStream* st, const char* msg, va_list ap) { int error_index = pointer_delta_as_int(_token, _line); if (error_index >= _line_len) { error_index = _line_len - 1; @@ -412,25 +425,34 @@ void ClassListParser::error(const char* msg, ...) { jio_vfprintf(defaultStream::error_stream(), msg, ap); if (_line_len <= 0) { - jio_fprintf(defaultStream::error_stream(), "\n"); + st->print("\n"); } else { - jio_fprintf(defaultStream::error_stream(), ":\n"); + st->print(":\n"); for (int i=0; i<_line_len; i++) { char c = _line[i]; if (c == '\0') { - jio_fprintf(defaultStream::error_stream(), "%s", " "); + st->print("%s", " "); } else { - jio_fprintf(defaultStream::error_stream(), "%c", c); + st->print("%c", c); } } - jio_fprintf(defaultStream::error_stream(), "\n"); + st->print("\n"); for (int i=0; iprint("%s", " "); } - jio_fprintf(defaultStream::error_stream(), "^\n"); + st->print("^\n"); } - va_end(ap); +} +void ClassListParser::error(const char* msg, ...) { + va_list ap; + va_start(ap, msg); + fileStream fs(defaultStream::error_stream()); + //TODO: we should write to UL/error instead, but that requires fixing some tests cases. + //LogTarget(Error, cds) lt; + //LogStream ls(lt); + print_diagnostic_info(&fs, msg, ap); + va_end(ap); vm_exit_during_initialization("class list format error.", nullptr); } @@ -453,6 +475,16 @@ void ClassListParser::check_class_name(const char* class_name) { } } +void ClassListParser::constant_pool_resolution_warning(const char* msg, ...) { + va_list ap; + va_start(ap, msg); + LogTarget(Warning, cds, resolve) lt; + LogStream ls(lt); + print_diagnostic_info(&ls, msg, ap); + ls.print("Your classlist may be out of sync with the JDK or the application."); + va_end(ap); +} + // This function is used for loading classes for customized class loaders // during archive dumping. InstanceKlass* ClassListParser::load_class_from_source(Symbol* class_name, TRAPS) { @@ -727,3 +759,92 @@ InstanceKlass* ClassListParser::lookup_interface_for_current_class(Symbol* inter ShouldNotReachHere(); return nullptr; } + +InstanceKlass* ClassListParser::find_builtin_class_helper(JavaThread* current, Symbol* class_name_symbol, oop class_loader_oop) { + Handle class_loader(current, class_loader_oop); + Handle protection_domain; + return SystemDictionary::find_instance_klass(current, class_name_symbol, class_loader, protection_domain); +} + +InstanceKlass* ClassListParser::find_builtin_class(JavaThread* current, const char* class_name) { + TempNewSymbol class_name_symbol = SymbolTable::new_symbol(class_name); + InstanceKlass* ik; + + if ( (ik = find_builtin_class_helper(current, class_name_symbol, nullptr)) != nullptr + || (ik = find_builtin_class_helper(current, class_name_symbol, SystemDictionary::java_platform_loader())) != nullptr + || (ik = find_builtin_class_helper(current, class_name_symbol, SystemDictionary::java_system_loader())) != nullptr) { + return ik; + } else { + return nullptr; + } +} + +void ClassListParser::parse_constant_pool_tag() { + if (parse_lambda_forms_invokers_only()) { + return; + } + + JavaThread* THREAD = JavaThread::current(); + skip_whitespaces(); + char* class_name = _token; + skip_non_whitespaces(); + *_token = '\0'; + _token ++; + + InstanceKlass* ik = find_builtin_class(THREAD, class_name); + if (ik == nullptr) { + _token = class_name; + if (strstr(class_name, "/$Proxy") != nullptr || + strstr(class_name, "MethodHandle$Species_") != nullptr) { + // ignore -- TODO: we should filter these out in classListWriter.cpp + } else { + constant_pool_resolution_warning("class %s is not (yet) loaded by one of the built-in loaders", class_name); + } + return; + } + + ResourceMark rm(THREAD); + constantPoolHandle cp(THREAD, ik->constants()); + GrowableArray preresolve_list(cp->length(), cp->length(), false); + bool preresolve_class = false; + bool preresolve_fmi = false; + bool preresolve_indy = false; + + while (*_token) { + int cp_index; + skip_whitespaces(); + parse_uint(&cp_index); + if (cp_index < 1 || cp_index >= cp->length()) { + constant_pool_resolution_warning("Invalid constant pool index %d", cp_index); + return; + } else { + preresolve_list.at_put(cp_index, true); + } + constantTag cp_tag = cp->tag_at(cp_index); + switch (cp_tag.value()) { + case JVM_CONSTANT_UnresolvedClass: + preresolve_class = true; + break; + case JVM_CONSTANT_UnresolvedClassInError: + case JVM_CONSTANT_Class: + // ignore + break; + case JVM_CONSTANT_Fieldref: + preresolve_fmi = true; + break; + break; + default: + constant_pool_resolution_warning("Unsupported constant pool index %d: %s (type=%d)", + cp_index, cp_tag.internal_name(), cp_tag.value()); + return; + } + } + + if (preresolve_class) { + ClassPrelinker::preresolve_class_cp_entries(THREAD, ik, &preresolve_list); + } + if (preresolve_fmi) { + ClassPrelinker::preresolve_field_and_method_cp_entries(THREAD, ik, &preresolve_list); + } +} + diff --git a/src/hotspot/share/cds/classListParser.hpp b/src/hotspot/share/cds/classListParser.hpp index 50ede4f6dff..540e61335d0 100644 --- a/src/hotspot/share/cds/classListParser.hpp +++ b/src/hotspot/share/cds/classListParser.hpp @@ -31,9 +31,6 @@ #include "utilities/istream.hpp" #include "utilities/resizeableResourceHash.hpp" -#define LAMBDA_PROXY_TAG "@lambda-proxy" -#define LAMBDA_FORM_TAG "@lambda-form-invoker" - class constantPoolHandle; class Thread; @@ -68,6 +65,10 @@ class CDSIndyInfo { }; class ClassListParser : public StackObj { + static const char* CONSTANT_POOL_TAG; + static const char* LAMBDA_FORM_TAG; + static const char* LAMBDA_PROXY_TAG; + public: enum ParseMode { _parse_all, @@ -117,17 +118,25 @@ class ClassListParser : public StackObj { void print_actual_interfaces(InstanceKlass *ik); bool is_matching_cp_entry(const constantPoolHandle &pool, int cp_index, TRAPS); + InstanceKlass* find_builtin_class_helper(JavaThread* current, Symbol* class_name_symbol, oop class_loader_oop); + InstanceKlass* find_builtin_class(JavaThread* current, const char* class_name); + void resolve_indy(JavaThread* current, Symbol* class_name_symbol); void resolve_indy_impl(Symbol* class_name_symbol, TRAPS); void clean_up_input_line(); void read_class_name_and_attributes(); void parse_class_name_and_attributes(TRAPS); Klass* load_current_class(Symbol* class_name_symbol, TRAPS); + void parse_constant_pool_tag(); size_t lineno() { return _input_stream.lineno(); } FILE* do_open(const char* file); ClassListParser(const char* file, ParseMode _parse_mode); ~ClassListParser(); + void print_diagnostic_info(outputStream* st, const char* msg, va_list ap) ATTRIBUTE_PRINTF(3, 0); + void print_diagnostic_info(outputStream* st, const char* msg, ...) ATTRIBUTE_PRINTF(3, 0); + void constant_pool_resolution_warning(const char* msg, ...) ATTRIBUTE_PRINTF(2, 0); + void error(const char* msg, ...) ATTRIBUTE_PRINTF(2, 0); public: static void parse_classlist(const char* classlist_path, ParseMode parse_mode, TRAPS) { @@ -141,13 +150,18 @@ class ClassListParser : public StackObj { assert(_instance != nullptr, "must be"); return _instance; } + static const char* lambda_proxy_tag() { + return LAMBDA_PROXY_TAG; + } + static const char* lambda_form_tag() { + return LAMBDA_FORM_TAG; + } void parse(TRAPS); void split_tokens_by_whitespace(int offset, GrowableArray* items); int split_at_tag_from_line(); void parse_at_tags(TRAPS); char* _token; - void error(const char* msg, ...); void parse_int(int* value); void parse_uint(int* value); bool try_parse_uint(int* value); diff --git a/src/hotspot/share/cds/classListWriter.cpp b/src/hotspot/share/cds/classListWriter.cpp index 2a65ee51d6e..97f0bc3476e 100644 --- a/src/hotspot/share/cds/classListWriter.cpp +++ b/src/hotspot/share/cds/classListWriter.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 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 @@ -25,12 +25,15 @@ #include "precompiled.hpp" #include "cds/cds_globals.hpp" #include "cds/classListWriter.hpp" +#include "cds/lambdaFormInvokers.inline.hpp" #include "classfile/classFileStream.hpp" #include "classfile/classLoader.hpp" #include "classfile/classLoaderData.hpp" +#include "classfile/classLoaderDataGraph.hpp" #include "classfile/moduleEntry.hpp" #include "classfile/systemDictionaryShared.hpp" #include "memory/resourceArea.hpp" +#include "oops/constantPool.inline.hpp" #include "oops/instanceKlass.hpp" #include "runtime/mutexLocker.hpp" @@ -189,3 +192,94 @@ void ClassListWriter::delete_classlist() { delete _classlist_file; } } + +class ClassListWriter::WriteResolveConstantsCLDClosure : public CLDClosure { +public: + void do_cld(ClassLoaderData* cld) { + for (Klass* klass = cld->klasses(); klass != nullptr; klass = klass->next_link()) { + if (klass->is_instance_klass()) { + InstanceKlass* ik = InstanceKlass::cast(klass); + write_resolved_constants_for(ik); + } + } + } +}; + +void ClassListWriter::write_resolved_constants() { + if (!is_enabled()) { + return; + } + MutexLocker lock(ClassLoaderDataGraph_lock); + MutexLocker lock2(ClassListFile_lock, Mutex::_no_safepoint_check_flag); + + WriteResolveConstantsCLDClosure closure; + ClassLoaderDataGraph::loaded_cld_do(&closure); +} + +void ClassListWriter::write_resolved_constants_for(InstanceKlass* ik) { + if (!SystemDictionaryShared::is_builtin_loader(ik->class_loader_data()) || + ik->is_hidden()) { + return; + } + if (LambdaFormInvokers::may_be_regenerated_class(ik->name())) { + return; + } + if (ik->name()->equals("jdk/internal/module/SystemModules$all")) { + // This class is regenerated during JDK build process, so the classlist + // may not match the version that's in the real jdk image. + return; + } + + if (!has_id(ik)) { // do not resolve CP for classes loaded by custom loaders. + return; + } + + ResourceMark rm; + ConstantPool* cp = ik->constants(); + GrowableArray list(cp->length(), cp->length(), false); + bool print = false; + + for (int cp_index = 1; cp_index < cp->length(); cp_index++) { // Index 0 is unused + switch (cp->tag_at(cp_index).value()) { + case JVM_CONSTANT_Class: + { + Klass* k = cp->resolved_klass_at(cp_index); + if (k->is_instance_klass()) { + list.at_put(cp_index, true); + print = true; + } + } + break; + } + } + + if (cp->cache() != nullptr) { + Array* field_entries = cp->cache()->resolved_field_entries(); + if (field_entries != nullptr) { + for (int i = 0; i < field_entries->length(); i++) { + ResolvedFieldEntry* rfe = field_entries->adr_at(i); + if (rfe->is_resolved(Bytecodes::_getstatic) || + rfe->is_resolved(Bytecodes::_putstatic) || + rfe->is_resolved(Bytecodes::_getfield) || + rfe->is_resolved(Bytecodes::_putfield)) { + list.at_put(rfe->constant_pool_index(), true); + print = true; + } + } + } + } + + if (print) { + outputStream* stream = _classlist_file; + stream->print("@cp %s", ik->name()->as_C_string()); + for (int i = 0; i < list.length(); i++) { + if (list.at(i)) { + constantTag cp_tag = cp->tag_at(i).value(); + assert(cp_tag.value() == JVM_CONSTANT_Class || + cp_tag.value() == JVM_CONSTANT_Fieldref, "sanity"); + stream->print(" %d", i); + } + } + stream->cr(); + } +} diff --git a/src/hotspot/share/cds/classListWriter.hpp b/src/hotspot/share/cds/classListWriter.hpp index a6c749fea7c..250e7ddf94b 100644 --- a/src/hotspot/share/cds/classListWriter.hpp +++ b/src/hotspot/share/cds/classListWriter.hpp @@ -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 @@ -34,6 +34,8 @@ class ClassFileStream; class ClassListWriter { #if INCLUDE_CDS class IDTable; + class WriteResolveConstantsCLDClosure; + static fileStream* _classlist_file; static IDTable* _id_table; static int _total_ids; @@ -42,6 +44,7 @@ class ClassListWriter { static int get_id(const InstanceKlass* k); static bool has_id(const InstanceKlass* k); static void assert_locked() { assert_lock_strong(ClassListFile_lock); } + static void write_resolved_constants_for(InstanceKlass* klass); public: ClassListWriter() : _locker(Thread::current(), ClassListFile_lock, Mutex::_no_safepoint_check_flag) {} @@ -66,6 +69,7 @@ class ClassListWriter { static void init() NOT_CDS_RETURN; static void write(const InstanceKlass* k, const ClassFileStream* cfs) NOT_CDS_RETURN; static void write_to_stream(const InstanceKlass* k, outputStream* stream, const ClassFileStream* cfs = nullptr) NOT_CDS_RETURN; + static void write_resolved_constants() NOT_CDS_RETURN; static void delete_classlist() NOT_CDS_RETURN; }; diff --git a/src/hotspot/share/cds/classPrelinker.cpp b/src/hotspot/share/cds/classPrelinker.cpp index 21946ebe9c0..223d3937f93 100644 --- a/src/hotspot/share/cds/classPrelinker.cpp +++ b/src/hotspot/share/cds/classPrelinker.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 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 @@ -26,8 +26,12 @@ #include "cds/archiveBuilder.hpp" #include "cds/cdsConfig.hpp" #include "cds/classPrelinker.hpp" +#include "cds/regeneratedClasses.hpp" #include "classfile/systemDictionary.hpp" +#include "classfile/systemDictionaryShared.hpp" #include "classfile/vmClasses.hpp" +#include "interpreter/bytecodeStream.hpp" +#include "interpreter/interpreterRuntime.hpp" #include "memory/resourceArea.hpp" #include "oops/constantPool.inline.hpp" #include "oops/instanceKlass.hpp" @@ -73,33 +77,52 @@ void ClassPrelinker::dispose() { _processed_classes = nullptr; } -bool ClassPrelinker::can_archive_resolved_klass(ConstantPool* cp, int cp_index) { +// Returns true if we CAN PROVE that cp_index will always resolve to +// the same information at both dump time and run time. This is a +// necessary (but not sufficient) condition for pre-resolving cp_index +// during CDS archive assembly. +bool ClassPrelinker::is_resolution_deterministic(ConstantPool* cp, int cp_index) { assert(!is_in_archivebuilder_buffer(cp), "sanity"); - assert(cp->tag_at(cp_index).is_klass(), "must be resolved"); - Klass* resolved_klass = cp->resolved_klass_at(cp_index); - assert(resolved_klass != nullptr, "must be"); + if (cp->tag_at(cp_index).is_klass()) { + // We require cp_index to be already resolved. This is fine for now, are we + // currently archive only CP entries that are already resolved. + Klass* resolved_klass = cp->resolved_klass_at(cp_index); + return resolved_klass != nullptr && is_class_resolution_deterministic(cp->pool_holder(), resolved_klass); + } else if (cp->tag_at(cp_index).is_field()) { + int klass_cp_index = cp->uncached_klass_ref_index_at(cp_index); + if (!cp->tag_at(klass_cp_index).is_klass()) { + // Not yet resolved + return false; + } + Klass* k = cp->resolved_klass_at(klass_cp_index); + if (!is_class_resolution_deterministic(cp->pool_holder(), k)) { + return false; + } - return can_archive_resolved_klass(cp->pool_holder(), resolved_klass); + if (!k->is_instance_klass()) { + // TODO: support non instance klasses as well. + return false; + } + + // Here, We don't check if this entry can actually be resolved to a valid Field/Method. + // This method should be called by the ConstantPool to check Fields/Methods that + // have already been successfully resolved. + return true; + } else { + return false; + } } -bool ClassPrelinker::can_archive_resolved_klass(InstanceKlass* cp_holder, Klass* resolved_klass) { +bool ClassPrelinker::is_class_resolution_deterministic(InstanceKlass* cp_holder, Klass* resolved_class) { assert(!is_in_archivebuilder_buffer(cp_holder), "sanity"); - assert(!is_in_archivebuilder_buffer(resolved_klass), "sanity"); - - if (resolved_klass->is_instance_klass()) { - InstanceKlass* ik = InstanceKlass::cast(resolved_klass); - if (is_vm_class(ik)) { // These are safe to resolve. See is_vm_class declaration. - assert(ik->is_shared_boot_class(), "vmClasses must be loaded by boot loader"); - if (cp_holder->is_shared_boot_class()) { - // For now, do this for boot loader only. Other loaders - // must go through ConstantPool::klass_at_impl at runtime - // to put this class in their directory. - - // TODO: we can support the platform and app loaders as well, if we - // preload the vmClasses into these two loaders during VM bootstrap. - return true; - } + assert(!is_in_archivebuilder_buffer(resolved_class), "sanity"); + + if (resolved_class->is_instance_klass()) { + InstanceKlass* ik = InstanceKlass::cast(resolved_class); + + if (!ik->is_shared() && SystemDictionaryShared::is_excluded_class(ik)) { + return false; } if (cp_holder->is_subtype_of(ik)) { @@ -108,20 +131,34 @@ bool ClassPrelinker::can_archive_resolved_klass(InstanceKlass* cp_holder, Klass* return true; } - // TODO -- allow objArray classes, too + if (is_vm_class(ik)) { + if (ik->class_loader() != cp_holder->class_loader()) { + // At runtime, cp_holder() may not be able to resolve to the same + // ik. For example, a different version of ik may be defined in + // cp->pool_holder()'s loader using MethodHandles.Lookup.defineClass(). + return false; + } else { + return true; + } + } + } else if (resolved_class->is_objArray_klass()) { + Klass* elem = ObjArrayKlass::cast(resolved_class)->bottom_klass(); + if (elem->is_instance_klass()) { + return is_class_resolution_deterministic(cp_holder, InstanceKlass::cast(elem)); + } else if (elem->is_typeArray_klass()) { + return true; + } + } else if (resolved_class->is_typeArray_klass()) { + return true; } return false; } void ClassPrelinker::dumptime_resolve_constants(InstanceKlass* ik, TRAPS) { - constantPoolHandle cp(THREAD, ik->constants()); - if (cp->cache() == nullptr || cp->reference_map() == nullptr) { - // The cache may be null if the pool_holder klass fails verification - // at dump time due to missing dependencies. + if (!ik->is_linked()) { return; } - bool first_time; _processed_classes->put_if_absent(ik, &first_time); if (!first_time) { @@ -129,12 +166,9 @@ void ClassPrelinker::dumptime_resolve_constants(InstanceKlass* ik, TRAPS) { return; } + constantPoolHandle cp(THREAD, ik->constants()); for (int cp_index = 1; cp_index < cp->length(); cp_index++) { // Index 0 is unused switch (cp->tag_at(cp_index).value()) { - case JVM_CONSTANT_UnresolvedClass: - maybe_resolve_class(cp, cp_index, CHECK); - break; - case JVM_CONSTANT_String: resolve_string(cp, cp_index, CHECK); // may throw OOM when interning strings. break; @@ -142,43 +176,33 @@ void ClassPrelinker::dumptime_resolve_constants(InstanceKlass* ik, TRAPS) { } } -Klass* ClassPrelinker::find_loaded_class(JavaThread* THREAD, oop class_loader, Symbol* name) { - HandleMark hm(THREAD); - Handle h_loader(THREAD, class_loader); - Klass* k = SystemDictionary::find_instance_or_array_klass(THREAD, name, +// This works only for the boot/platform/app loaders +Klass* ClassPrelinker::find_loaded_class(Thread* current, oop class_loader, Symbol* name) { + HandleMark hm(current); + Handle h_loader(current, class_loader); + Klass* k = SystemDictionary::find_instance_or_array_klass(current, name, h_loader, Handle()); if (k != nullptr) { return k; } - if (class_loader == SystemDictionary::java_system_loader()) { - return find_loaded_class(THREAD, SystemDictionary::java_platform_loader(), name); - } else if (class_loader == SystemDictionary::java_platform_loader()) { - return find_loaded_class(THREAD, nullptr, name); + if (h_loader() == SystemDictionary::java_system_loader()) { + return find_loaded_class(current, SystemDictionary::java_platform_loader(), name); + } else if (h_loader() == SystemDictionary::java_platform_loader()) { + return find_loaded_class(current, nullptr, name); + } else { + assert(h_loader() == nullptr, "This function only works for boot/platform/app loaders %p %p %p", + cast_from_oop
(h_loader()), + cast_from_oop
(SystemDictionary::java_system_loader()), + cast_from_oop
(SystemDictionary::java_platform_loader())); } return nullptr; } -Klass* ClassPrelinker::maybe_resolve_class(constantPoolHandle cp, int cp_index, TRAPS) { - assert(!is_in_archivebuilder_buffer(cp()), "sanity"); - InstanceKlass* cp_holder = cp->pool_holder(); - if (!cp_holder->is_shared_boot_class() && - !cp_holder->is_shared_platform_class() && - !cp_holder->is_shared_app_class()) { - // Don't trust custom loaders, as they may not be well-behaved - // when resolving classes. - return nullptr; - } - - Symbol* name = cp->klass_name_at(cp_index); - Klass* resolved_klass = find_loaded_class(THREAD, cp_holder->class_loader(), name); - if (resolved_klass != nullptr && can_archive_resolved_klass(cp_holder, resolved_klass)) { - Klass* k = cp->klass_at(cp_index, CHECK_NULL); // Should fail only with OOM - assert(k == resolved_klass, "must be"); - } - - return resolved_klass; +Klass* ClassPrelinker::find_loaded_class(Thread* current, ConstantPool* cp, int class_cp_index) { + Symbol* name = cp->klass_name_at(class_cp_index); + return find_loaded_class(current, cp->pool_holder()->class_loader(), name); } #if INCLUDE_CDS_JAVA_HEAP @@ -190,6 +214,110 @@ void ClassPrelinker::resolve_string(constantPoolHandle cp, int cp_index, TRAPS) } #endif +void ClassPrelinker::preresolve_class_cp_entries(JavaThread* current, InstanceKlass* ik, GrowableArray* preresolve_list) { + if (!SystemDictionaryShared::is_builtin_loader(ik->class_loader_data())) { + return; + } + + JavaThread* THREAD = current; + constantPoolHandle cp(THREAD, ik->constants()); + for (int cp_index = 1; cp_index < cp->length(); cp_index++) { + if (cp->tag_at(cp_index).value() == JVM_CONSTANT_UnresolvedClass) { + if (preresolve_list != nullptr && preresolve_list->at(cp_index) == false) { + // This class was not resolved during trial run. Don't attempt to resolve it. Otherwise + // the compiler may generate less efficient code. + continue; + } + if (find_loaded_class(current, cp(), cp_index) == nullptr) { + // Do not resolve any class that has not been loaded yet + continue; + } + Klass* resolved_klass = cp->klass_at(cp_index, THREAD); + if (HAS_PENDING_EXCEPTION) { + CLEAR_PENDING_EXCEPTION; // just ignore + } else { + log_trace(cds, resolve)("Resolved class [%3d] %s -> %s", cp_index, ik->external_name(), + resolved_klass->external_name()); + } + } + } +} + +void ClassPrelinker::preresolve_field_and_method_cp_entries(JavaThread* current, InstanceKlass* ik, GrowableArray* preresolve_list) { + JavaThread* THREAD = current; + constantPoolHandle cp(THREAD, ik->constants()); + if (cp->cache() == nullptr) { + return; + } + for (int i = 0; i < ik->methods()->length(); i++) { + Method* m = ik->methods()->at(i); + BytecodeStream bcs(methodHandle(THREAD, m)); + while (!bcs.is_last_bytecode()) { + bcs.next(); + Bytecodes::Code raw_bc = bcs.raw_code(); + switch (raw_bc) { + case Bytecodes::_getfield: + case Bytecodes::_putfield: + maybe_resolve_fmi_ref(ik, m, raw_bc, bcs.get_index_u2(), preresolve_list, THREAD); + if (HAS_PENDING_EXCEPTION) { + CLEAR_PENDING_EXCEPTION; // just ignore + } + break; + default: + break; + } + } + } +} + +void ClassPrelinker::maybe_resolve_fmi_ref(InstanceKlass* ik, Method* m, Bytecodes::Code bc, int raw_index, + GrowableArray* preresolve_list, TRAPS) { + methodHandle mh(THREAD, m); + constantPoolHandle cp(THREAD, ik->constants()); + HandleMark hm(THREAD); + int cp_index = cp->to_cp_index(raw_index, bc); + + if (cp->is_resolved(raw_index, bc)) { + return; + } + + if (preresolve_list != nullptr && preresolve_list->at(cp_index) == false) { + // This field wasn't resolved during the trial run. Don't attempt to resolve it. Otherwise + // the compiler may generate less efficient code. + return; + } + + int klass_cp_index = cp->uncached_klass_ref_index_at(cp_index); + if (find_loaded_class(THREAD, cp(), klass_cp_index) == nullptr) { + // Do not resolve any field/methods from a class that has not been loaded yet. + return; + } + + Klass* resolved_klass = cp->klass_ref_at(raw_index, bc, CHECK); + + switch (bc) { + case Bytecodes::_getfield: + case Bytecodes::_putfield: + InterpreterRuntime::resolve_get_put(bc, raw_index, mh, cp, false /*initialize_holder*/, CHECK); + break; + + default: + ShouldNotReachHere(); + } + + if (log_is_enabled(Trace, cds, resolve)) { + ResourceMark rm(THREAD); + bool resolved = cp->is_resolved(raw_index, bc); + Symbol* name = cp->name_ref_at(raw_index, bc); + Symbol* signature = cp->signature_ref_at(raw_index, bc); + log_trace(cds, resolve)("%s %s [%3d] %s -> %s.%s:%s", + (resolved ? "Resolved" : "Failed to resolve"), + Bytecodes::name(bc), cp_index, ik->external_name(), + resolved_klass->external_name(), + name->as_C_string(), signature->as_C_string()); + } +} + #ifdef ASSERT bool ClassPrelinker::is_in_archivebuilder_buffer(address p) { if (!Thread::current()->is_VM_thread() || ArchiveBuilder::current() == nullptr) { diff --git a/src/hotspot/share/cds/classPrelinker.hpp b/src/hotspot/share/cds/classPrelinker.hpp index 7a4f36386ea..41588961d8b 100644 --- a/src/hotspot/share/cds/classPrelinker.hpp +++ b/src/hotspot/share/cds/classPrelinker.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 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 @@ -25,6 +25,7 @@ #ifndef SHARE_CDS_CLASSPRELINKER_HPP #define SHARE_CDS_CLASSPRELINKER_HPP +#include "interpreter/bytecodes.hpp" #include "oops/oopsHierarchy.hpp" #include "memory/allStatic.hpp" #include "memory/allocation.hpp" @@ -64,14 +65,21 @@ class ClassPrelinker : AllStatic { return is_in_archivebuilder_buffer((address)(p)); } static void resolve_string(constantPoolHandle cp, int cp_index, TRAPS) NOT_CDS_JAVA_HEAP_RETURN; - static Klass* maybe_resolve_class(constantPoolHandle cp, int cp_index, TRAPS); - static bool can_archive_resolved_klass(InstanceKlass* cp_holder, Klass* resolved_klass); - static Klass* find_loaded_class(JavaThread* THREAD, oop class_loader, Symbol* name); + static bool is_class_resolution_deterministic(InstanceKlass* cp_holder, Klass* resolved_class); + static Klass* find_loaded_class(Thread* current, oop class_loader, Symbol* name); + static Klass* find_loaded_class(Thread* current, ConstantPool* cp, int class_cp_index); + + // fmi = FieldRef/MethodRef/InterfaceMethodRef + static void maybe_resolve_fmi_ref(InstanceKlass* ik, Method* m, Bytecodes::Code bc, int raw_index, + GrowableArray* resolve_fmi_list, TRAPS); public: static void initialize(); static void dispose(); + static void preresolve_class_cp_entries(JavaThread* current, InstanceKlass* ik, GrowableArray* preresolve_list); + static void preresolve_field_and_method_cp_entries(JavaThread* current, InstanceKlass* ik, GrowableArray* preresolve_list); + // Is this class resolved as part of vmClasses::resolve_all()? If so, these // classes are guatanteed to be loaded at runtime (and cannot be replaced by JVMTI) // when CDS is enabled. Therefore, we can safely keep a direct reference to these @@ -82,10 +90,7 @@ class ClassPrelinker : AllStatic { // CDS archive. static void dumptime_resolve_constants(InstanceKlass* ik, TRAPS); - // Can we resolve the klass entry at cp_index in this constant pool, and store - // the result in the CDS archive? Returns true if cp_index is guaranteed to - // resolve to the same InstanceKlass* at both dump time and run time. - static bool can_archive_resolved_klass(ConstantPool* cp, int cp_index); + static bool is_resolution_deterministic(ConstantPool* cp, int cp_index); }; #endif // SHARE_CDS_CLASSPRELINKER_HPP diff --git a/src/hotspot/share/cds/cppVtables.cpp b/src/hotspot/share/cds/cppVtables.cpp index f17d94a82fd..6dd5e65ae43 100644 --- a/src/hotspot/share/cds/cppVtables.cpp +++ b/src/hotspot/share/cds/cppVtables.cpp @@ -66,19 +66,17 @@ class CppVtableInfo { intptr_t _vtable_size; - intptr_t _cloned_vtable[1]; + intptr_t _cloned_vtable[1]; // Pseudo flexible array member. + static size_t cloned_vtable_offset() { return offset_of(CppVtableInfo, _cloned_vtable); } public: - static int num_slots(int vtable_size) { - return 1 + vtable_size; // Need to add the space occupied by _vtable_size; - } int vtable_size() { return int(uintx(_vtable_size)); } void set_vtable_size(int n) { _vtable_size = intptr_t(n); } - intptr_t* cloned_vtable() { return &_cloned_vtable[0]; } - void zero() { memset(_cloned_vtable, 0, sizeof(intptr_t) * vtable_size()); } + // Using _cloned_vtable[i] for i > 0 causes undefined behavior. We use address calculation instead. + intptr_t* cloned_vtable() { return (intptr_t*)((char*)this + cloned_vtable_offset()); } + void zero() { memset(cloned_vtable(), 0, sizeof(intptr_t) * vtable_size()); } // Returns the address of the next CppVtableInfo that can be placed immediately after this CppVtableInfo static size_t byte_size(int vtable_size) { - CppVtableInfo i; - return pointer_delta(&i._cloned_vtable[vtable_size], &i, sizeof(u1)); + return cloned_vtable_offset() + (sizeof(intptr_t) * vtable_size); } }; diff --git a/src/hotspot/share/cds/dumpAllocStats.cpp b/src/hotspot/share/cds/dumpAllocStats.cpp index 21a40ca8f28..30ef1597063 100644 --- a/src/hotspot/share/cds/dumpAllocStats.cpp +++ b/src/hotspot/share/cds/dumpAllocStats.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2022, 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 @@ -102,8 +102,12 @@ void DumpAllocStats::print_stats(int ro_all, int rw_all) { #undef fmt_stats - msg.debug("Class CP entries = %d, archived = %d (%3.1f%%)", - _num_klass_cp_entries, _num_klass_cp_entries_archived, - percent_of(_num_klass_cp_entries_archived, _num_klass_cp_entries)); - + msg.info("Class CP entries = %6d, archived = %6d (%5.1f%%), reverted = %6d", + _num_klass_cp_entries, _num_klass_cp_entries_archived, + percent_of(_num_klass_cp_entries_archived, _num_klass_cp_entries), + _num_klass_cp_entries_reverted); + msg.info("Field CP entries = %6d, archived = %6d (%5.1f%%), reverted = %6d", + _num_field_cp_entries, _num_field_cp_entries_archived, + percent_of(_num_field_cp_entries_archived, _num_field_cp_entries), + _num_field_cp_entries_reverted); } diff --git a/src/hotspot/share/cds/dumpAllocStats.hpp b/src/hotspot/share/cds/dumpAllocStats.hpp index f6e170cdef2..424a98aa738 100644 --- a/src/hotspot/share/cds/dumpAllocStats.hpp +++ b/src/hotspot/share/cds/dumpAllocStats.hpp @@ -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 @@ -65,8 +65,12 @@ class DumpAllocStats : public StackObj { int _counts[2][_number_of_types]; int _bytes [2][_number_of_types]; + int _num_field_cp_entries; + int _num_field_cp_entries_archived; + int _num_field_cp_entries_reverted; int _num_klass_cp_entries; int _num_klass_cp_entries_archived; + int _num_klass_cp_entries_reverted; public: enum { RO = 0, RW = 1 }; @@ -74,8 +78,12 @@ class DumpAllocStats : public StackObj { DumpAllocStats() { memset(_counts, 0, sizeof(_counts)); memset(_bytes, 0, sizeof(_bytes)); - _num_klass_cp_entries = 0; - _num_klass_cp_entries_archived = 0; + _num_field_cp_entries = 0; + _num_field_cp_entries_archived = 0; + _num_field_cp_entries_reverted = 0; + _num_klass_cp_entries = 0; + _num_klass_cp_entries_archived = 0; + _num_klass_cp_entries_reverted = 0; }; CompactHashtableStats* symbol_stats() { return &_symbol_stats; } @@ -102,9 +110,16 @@ class DumpAllocStats : public StackObj { _bytes[RW][CppVTablesType] += byte_size; } - void record_klass_cp_entry(bool archived) { + void record_field_cp_entry(bool archived, bool reverted) { + _num_field_cp_entries ++; + _num_field_cp_entries_archived += archived ? 1 : 0; + _num_field_cp_entries_reverted += reverted ? 1 : 0; + } + + void record_klass_cp_entry(bool archived, bool reverted) { _num_klass_cp_entries ++; _num_klass_cp_entries_archived += archived ? 1 : 0; + _num_klass_cp_entries_reverted += reverted ? 1 : 0; } void print_stats(int ro_all, int rw_all); diff --git a/src/hotspot/share/cds/dynamicArchive.cpp b/src/hotspot/share/cds/dynamicArchive.cpp index 5effa4e458c..04c60b89580 100644 --- a/src/hotspot/share/cds/dynamicArchive.cpp +++ b/src/hotspot/share/cds/dynamicArchive.cpp @@ -31,6 +31,7 @@ #include "cds/classPrelinker.hpp" #include "cds/dynamicArchive.hpp" #include "cds/regeneratedClasses.hpp" +#include "classfile/classLoader.hpp" #include "classfile/classLoaderData.inline.hpp" #include "classfile/symbolTable.hpp" #include "classfile/systemDictionaryShared.hpp" @@ -118,6 +119,9 @@ class DynamicArchiveBuilder : public ArchiveBuilder { return; } + log_info(cds,dynamic)("CDS dynamic dump: clinit = " JLONG_FORMAT "ms)", + ClassLoader::class_init_time_ms()); + init_header(); gather_source_objs(); gather_array_klasses(); diff --git a/src/hotspot/share/cds/lambdaFormInvokers.hpp b/src/hotspot/share/cds/lambdaFormInvokers.hpp index 7bb5e5932c7..e78ddb1a1bc 100644 --- a/src/hotspot/share/cds/lambdaFormInvokers.hpp +++ b/src/hotspot/share/cds/lambdaFormInvokers.hpp @@ -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 @@ -32,6 +32,7 @@ class ClassFileStream; template class Array; +class SerializeClosure; class LambdaFormInvokers : public AllStatic { private: @@ -46,5 +47,6 @@ class LambdaFormInvokers : public AllStatic { static void regenerate_holder_classes(TRAPS); static void serialize(SerializeClosure* soc); static void cleanup_regenerated_classes(); + inline static bool may_be_regenerated_class(Symbol* name); }; #endif // SHARE_CDS_LAMBDAFORMINVOKERS_HPP diff --git a/src/hotspot/share/cds/lambdaFormInvokers.inline.hpp b/src/hotspot/share/cds/lambdaFormInvokers.inline.hpp new file mode 100644 index 00000000000..dddd0e36c47 --- /dev/null +++ b/src/hotspot/share/cds/lambdaFormInvokers.inline.hpp @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2023, 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. + * + */ + +#ifndef SHARE_CDS_LAMBDAFORMINVOKERS_INLINE_HPP +#define SHARE_CDS_LAMBDAFORMINVOKERS_INLINE_HPP + +#include "cds/lambdaFormInvokers.hpp" +#include "classfile/vmSymbols.hpp" + +inline bool LambdaFormInvokers::may_be_regenerated_class(Symbol* name) { + return name == vmSymbols::java_lang_invoke_Invokers_Holder() || + name == vmSymbols::java_lang_invoke_DirectMethodHandle_Holder() || + name == vmSymbols::java_lang_invoke_LambdaForm_Holder() || + name == vmSymbols::java_lang_invoke_DelegatingMethodHandle_Holder(); +} + +#endif // SHARE_CDS_LAMBDAFORMINVOKERS_INLINE_HPP diff --git a/src/hotspot/share/ci/ciInstanceKlass.cpp b/src/hotspot/share/ci/ciInstanceKlass.cpp index fa084e2287f..240bb25ae3a 100644 --- a/src/hotspot/share/ci/ciInstanceKlass.cpp +++ b/src/hotspot/share/ci/ciInstanceKlass.cpp @@ -661,7 +661,8 @@ class StaticFinalFieldPrinter : public FieldClosure { ResourceMark rm; oop mirror = fd->field_holder()->java_mirror(); _out->print("staticfield %s %s %s ", _holder, fd->name()->as_quoted_ascii(), fd->signature()->as_quoted_ascii()); - switch (fd->field_type()) { + BasicType field_type = fd->field_type(); + switch (field_type) { case T_BYTE: _out->print_cr("%d", mirror->byte_field(fd->offset())); break; case T_BOOLEAN: _out->print_cr("%d", mirror->bool_field(fd->offset())); break; case T_SHORT: _out->print_cr("%d", mirror->short_field(fd->offset())); break; @@ -682,9 +683,12 @@ class StaticFinalFieldPrinter : public FieldClosure { case T_OBJECT: { oop value = mirror->obj_field_acquire(fd->offset()); if (value == nullptr) { - _out->print_cr("null"); + if (field_type == T_ARRAY) { + _out->print("%d", -1); + } + _out->cr(); } else if (value->is_instance()) { - assert(fd->field_type() == T_OBJECT, ""); + assert(field_type == T_OBJECT, ""); if (value->is_a(vmClasses::String_klass())) { const char* ascii_value = java_lang_String::as_quoted_ascii(value); _out->print_cr("\"%s\"", (ascii_value != nullptr) ? ascii_value : ""); diff --git a/src/hotspot/share/ci/ciReplay.cpp b/src/hotspot/share/ci/ciReplay.cpp index 5fa30f86411..3ed71806b07 100644 --- a/src/hotspot/share/ci/ciReplay.cpp +++ b/src/hotspot/share/ci/ciReplay.cpp @@ -1056,46 +1056,48 @@ class CompileReplay : public StackObj { int length = parse_int("array length"); oop value = nullptr; - if (field_signature[1] == JVM_SIGNATURE_ARRAY) { - // multi dimensional array - ArrayKlass* kelem = (ArrayKlass *)parse_klass(CHECK); - if (kelem == nullptr) { - return; - } - int rank = 0; - while (field_signature[rank] == JVM_SIGNATURE_ARRAY) { - rank++; - } - jint* dims = NEW_RESOURCE_ARRAY(jint, rank); - dims[0] = length; - for (int i = 1; i < rank; i++) { - dims[i] = 1; // These aren't relevant to the compiler - } - value = kelem->multi_allocate(rank, dims, CHECK); - } else { - if (strcmp(field_signature, "[B") == 0) { - value = oopFactory::new_byteArray(length, CHECK); - } else if (strcmp(field_signature, "[Z") == 0) { - value = oopFactory::new_boolArray(length, CHECK); - } else if (strcmp(field_signature, "[C") == 0) { - value = oopFactory::new_charArray(length, CHECK); - } else if (strcmp(field_signature, "[S") == 0) { - value = oopFactory::new_shortArray(length, CHECK); - } else if (strcmp(field_signature, "[F") == 0) { - value = oopFactory::new_floatArray(length, CHECK); - } else if (strcmp(field_signature, "[D") == 0) { - value = oopFactory::new_doubleArray(length, CHECK); - } else if (strcmp(field_signature, "[I") == 0) { - value = oopFactory::new_intArray(length, CHECK); - } else if (strcmp(field_signature, "[J") == 0) { - value = oopFactory::new_longArray(length, CHECK); - } else if (field_signature[0] == JVM_SIGNATURE_ARRAY && - field_signature[1] == JVM_SIGNATURE_CLASS) { - parse_klass(CHECK); // eat up the array class name - Klass* kelem = resolve_klass(field_signature + 1, CHECK); - value = oopFactory::new_objArray(kelem, length, CHECK); + if (length != -1) { + if (field_signature[1] == JVM_SIGNATURE_ARRAY) { + // multi dimensional array + ArrayKlass* kelem = (ArrayKlass *)parse_klass(CHECK); + if (kelem == nullptr) { + return; + } + int rank = 0; + while (field_signature[rank] == JVM_SIGNATURE_ARRAY) { + rank++; + } + jint* dims = NEW_RESOURCE_ARRAY(jint, rank); + dims[0] = length; + for (int i = 1; i < rank; i++) { + dims[i] = 1; // These aren't relevant to the compiler + } + value = kelem->multi_allocate(rank, dims, CHECK); } else { - report_error("unhandled array staticfield"); + if (strcmp(field_signature, "[B") == 0) { + value = oopFactory::new_byteArray(length, CHECK); + } else if (strcmp(field_signature, "[Z") == 0) { + value = oopFactory::new_boolArray(length, CHECK); + } else if (strcmp(field_signature, "[C") == 0) { + value = oopFactory::new_charArray(length, CHECK); + } else if (strcmp(field_signature, "[S") == 0) { + value = oopFactory::new_shortArray(length, CHECK); + } else if (strcmp(field_signature, "[F") == 0) { + value = oopFactory::new_floatArray(length, CHECK); + } else if (strcmp(field_signature, "[D") == 0) { + value = oopFactory::new_doubleArray(length, CHECK); + } else if (strcmp(field_signature, "[I") == 0) { + value = oopFactory::new_intArray(length, CHECK); + } else if (strcmp(field_signature, "[J") == 0) { + value = oopFactory::new_longArray(length, CHECK); + } else if (field_signature[0] == JVM_SIGNATURE_ARRAY && + field_signature[1] == JVM_SIGNATURE_CLASS) { + Klass* actual_array_klass = parse_klass(CHECK); + Klass* kelem = ObjArrayKlass::cast(actual_array_klass)->element_klass(); + value = oopFactory::new_objArray(kelem, length, CHECK); + } else { + report_error("unhandled array staticfield"); + } } } java_mirror->obj_field_put(fd.offset(), value); @@ -1133,8 +1135,11 @@ class CompileReplay : public StackObj { Handle value = java_lang_String::create_from_str(string_value, CHECK); java_mirror->obj_field_put(fd.offset(), value()); } else if (field_signature[0] == JVM_SIGNATURE_CLASS) { - Klass* k = resolve_klass(string_value, CHECK); - oop value = InstanceKlass::cast(k)->allocate_instance(CHECK); + oop value = nullptr; + if (string_value != nullptr) { + Klass* k = resolve_klass(string_value, CHECK); + value = InstanceKlass::cast(k)->allocate_instance(CHECK); + } java_mirror->obj_field_put(fd.offset(), value); } else { report_error("unhandled staticfield"); diff --git a/src/hotspot/share/classfile/classLoader.cpp b/src/hotspot/share/classfile/classLoader.cpp index b317aaa071c..910cbe48c5c 100644 --- a/src/hotspot/share/classfile/classLoader.cpp +++ b/src/hotspot/share/classfile/classLoader.cpp @@ -78,6 +78,7 @@ #include "utilities/classpathStream.hpp" #include "utilities/events.hpp" #include "utilities/macros.hpp" +#include "utilities/ostream.hpp" #include "utilities/utf8.hpp" // Entry point in java.dll for path canonicalization @@ -118,9 +119,40 @@ PerfCounter* ClassLoader::_perf_define_appclass_time = nullptr; PerfCounter* ClassLoader::_perf_define_appclass_selftime = nullptr; PerfCounter* ClassLoader::_perf_app_classfile_bytes_read = nullptr; PerfCounter* ClassLoader::_perf_sys_classfile_bytes_read = nullptr; +PerfCounter* ClassLoader::_perf_ik_link_methods_time = nullptr; +PerfCounter* ClassLoader::_perf_method_adapters_time = nullptr; +PerfCounter* ClassLoader::_perf_ik_link_methods_count = nullptr; +PerfCounter* ClassLoader::_perf_method_adapters_count = nullptr; PerfCounter* ClassLoader::_unsafe_defineClassCallCounter = nullptr; PerfCounter* ClassLoader::_perf_secondary_hash_time = nullptr; +PerfCounter* ClassLoader::_perf_resolve_indy_time = nullptr; +PerfCounter* ClassLoader::_perf_resolve_invokehandle_time = nullptr; +PerfCounter* ClassLoader::_perf_resolve_mh_time = nullptr; +PerfCounter* ClassLoader::_perf_resolve_mt_time = nullptr; + +PerfCounter* ClassLoader::_perf_resolve_indy_count = nullptr; +PerfCounter* ClassLoader::_perf_resolve_invokehandle_count = nullptr; +PerfCounter* ClassLoader::_perf_resolve_mh_count = nullptr; +PerfCounter* ClassLoader::_perf_resolve_mt_count = nullptr; + +void ClassLoader::print_counters(outputStream *st) { + // The counters are only active if the logging is enabled, but + // we print to the passed in outputStream as requested. + if (log_is_enabled(Info, perf, class, link)) { + st->print_cr("ClassLoader:"); + st->print_cr(" clinit: " JLONG_FORMAT "ms / " JLONG_FORMAT " events", ClassLoader::class_init_time_ms(), ClassLoader::class_init_count()); + st->print_cr(" link methods: " JLONG_FORMAT "ms / " JLONG_FORMAT " events", Management::ticks_to_ms(_perf_ik_link_methods_time->get_value()) , _perf_ik_link_methods_count->get_value()); + st->print_cr(" method adapters: " JLONG_FORMAT "ms / " JLONG_FORMAT " events", Management::ticks_to_ms(_perf_method_adapters_time->get_value()) , _perf_method_adapters_count->get_value()); + st->print_cr(" resolve..."); + st->print_cr(" invokedynamic: " JLONG_FORMAT "ms / " JLONG_FORMAT " events", Management::ticks_to_ms(_perf_resolve_indy_time->get_value()) , _perf_resolve_indy_count->get_value()); + st->print_cr(" invokehandle: " JLONG_FORMAT "ms / " JLONG_FORMAT " events", Management::ticks_to_ms(_perf_resolve_invokehandle_time->get_value()) , _perf_resolve_invokehandle_count->get_value()); + st->print_cr(" CP_MethodHandle: " JLONG_FORMAT "ms / " JLONG_FORMAT " events", Management::ticks_to_ms(_perf_resolve_mh_time->get_value()) , _perf_resolve_mh_count->get_value()); + st->print_cr(" CP_MethodType: " JLONG_FORMAT "ms / " JLONG_FORMAT " events", Management::ticks_to_ms(_perf_resolve_mt_time->get_value()) , _perf_resolve_mt_count->get_value()); + st->cr(); + } +} + GrowableArray* ClassLoader::_patch_mod_entries = nullptr; GrowableArray* ClassLoader::_exploded_entries = nullptr; ClassPathEntry* ClassLoader::_jrt_entry = nullptr; @@ -1336,9 +1368,25 @@ void ClassLoader::initialize(TRAPS) { NEWPERFTICKCOUNTER(_perf_define_appclass_selftime, SUN_CLS, "defineAppClassTime.self"); NEWPERFBYTECOUNTER(_perf_app_classfile_bytes_read, SUN_CLS, "appClassBytes"); NEWPERFBYTECOUNTER(_perf_sys_classfile_bytes_read, SUN_CLS, "sysClassBytes"); - NEWPERFEVENTCOUNTER(_unsafe_defineClassCallCounter, SUN_CLS, "unsafeDefineClassCalls"); NEWPERFTICKCOUNTER(_perf_secondary_hash_time, SUN_CLS, "secondarySuperHashTime"); + + if (log_is_enabled(Info, perf, class, link)) { + NEWPERFTICKCOUNTER(_perf_ik_link_methods_time, SUN_CLS, "linkMethodsTime"); + NEWPERFTICKCOUNTER(_perf_method_adapters_time, SUN_CLS, "makeAdaptersTime"); + NEWPERFEVENTCOUNTER(_perf_ik_link_methods_count, SUN_CLS, "linkMethodsCount"); + NEWPERFEVENTCOUNTER(_perf_method_adapters_count, SUN_CLS, "makeAdaptersCount"); + + NEWPERFTICKCOUNTER(_perf_resolve_indy_time, SUN_CLS, "resolve_invokedynamic_time"); + NEWPERFTICKCOUNTER(_perf_resolve_invokehandle_time, SUN_CLS, "resolve_invokehandle_time"); + NEWPERFTICKCOUNTER(_perf_resolve_mh_time, SUN_CLS, "resolve_MethodHandle_time"); + NEWPERFTICKCOUNTER(_perf_resolve_mt_time, SUN_CLS, "resolve_MethodType_time"); + + NEWPERFEVENTCOUNTER(_perf_resolve_indy_count, SUN_CLS, "resolve_invokedynamic_count"); + NEWPERFEVENTCOUNTER(_perf_resolve_invokehandle_count, SUN_CLS, "resolve_invokehandle_count"); + NEWPERFEVENTCOUNTER(_perf_resolve_mh_count, SUN_CLS, "resolve_MethodHandle_count"); + NEWPERFEVENTCOUNTER(_perf_resolve_mt_count, SUN_CLS, "resolve_MethodType_count"); + } } // lookup java library entry points diff --git a/src/hotspot/share/classfile/classLoader.hpp b/src/hotspot/share/classfile/classLoader.hpp index d3ca476ac95..af625082dda 100644 --- a/src/hotspot/share/classfile/classLoader.hpp +++ b/src/hotspot/share/classfile/classLoader.hpp @@ -30,6 +30,7 @@ #include "runtime/perfDataTypes.hpp" #include "utilities/exceptions.hpp" #include "utilities/macros.hpp" +#include "utilities/ostream.hpp" #include "utilities/zipLibrary.hpp" // The VM class loader. @@ -166,6 +167,20 @@ class ClassLoader: AllStatic { static PerfCounter* _perf_define_appclass_selftime; static PerfCounter* _perf_app_classfile_bytes_read; static PerfCounter* _perf_sys_classfile_bytes_read; + static PerfCounter* _perf_ik_link_methods_time; + static PerfCounter* _perf_method_adapters_time; + static PerfCounter* _perf_ik_link_methods_count; + static PerfCounter* _perf_method_adapters_count; + + static PerfCounter* _perf_resolve_indy_time; + static PerfCounter* _perf_resolve_invokehandle_time; + static PerfCounter* _perf_resolve_mh_time; + static PerfCounter* _perf_resolve_mt_time; + + static PerfCounter* _perf_resolve_indy_count; + static PerfCounter* _perf_resolve_invokehandle_count; + static PerfCounter* _perf_resolve_mh_count; + static PerfCounter* _perf_resolve_mt_count; static PerfCounter* _unsafe_defineClassCallCounter; @@ -285,6 +300,23 @@ class ClassLoader: AllStatic { static PerfCounter* perf_app_classfile_bytes_read() { return _perf_app_classfile_bytes_read; } static PerfCounter* perf_sys_classfile_bytes_read() { return _perf_sys_classfile_bytes_read; } + static PerfCounter* perf_ik_link_methods_time() { return _perf_ik_link_methods_time; } + static PerfCounter* perf_method_adapters_time() { return _perf_method_adapters_time; } + static PerfCounter* perf_ik_link_methods_count() { return _perf_ik_link_methods_count; } + static PerfCounter* perf_method_adapters_count() { return _perf_method_adapters_count; } + + static PerfCounter* perf_resolve_invokedynamic_time() { return _perf_resolve_indy_time; } + static PerfCounter* perf_resolve_invokehandle_time() { return _perf_resolve_invokehandle_time; } + static PerfCounter* perf_resolve_method_handle_time() { return _perf_resolve_mh_time; } + static PerfCounter* perf_resolve_method_type_time() { return _perf_resolve_mt_time; } + + static PerfCounter* perf_resolve_invokedynamic_count() { return _perf_resolve_indy_count; } + static PerfCounter* perf_resolve_invokehandle_count() { return _perf_resolve_invokehandle_count; } + static PerfCounter* perf_resolve_method_handle_count() { return _perf_resolve_mh_count; } + static PerfCounter* perf_resolve_method_type_count() { return _perf_resolve_mt_count; } + + static void print_counters(outputStream *st); + // Record how many calls to Unsafe_DefineClass static PerfCounter* unsafe_defineClassCallCounter() { return _unsafe_defineClassCallCounter; diff --git a/src/hotspot/share/gc/g1/g1ConcurrentMark.inline.hpp b/src/hotspot/share/gc/g1/g1ConcurrentMark.inline.hpp index 9f165cf1d22..7b9be48d9b5 100644 --- a/src/hotspot/share/gc/g1/g1ConcurrentMark.inline.hpp +++ b/src/hotspot/share/gc/g1/g1ConcurrentMark.inline.hpp @@ -214,17 +214,14 @@ inline HeapWord* G1ConcurrentMark::top_at_rebuild_start(G1HeapRegion* r) const { } inline void G1ConcurrentMark::update_top_at_rebuild_start(G1HeapRegion* r) { + assert(r->is_old() || r->is_humongous(), "precondition"); + uint const region = r->hrm_index(); assert(region < _g1h->max_reserved_regions(), "Tried to access TARS for region %u out of bounds", region); assert(_top_at_rebuild_starts[region] == nullptr, "TARS for region %u has already been set to " PTR_FORMAT " should be null", region, p2i(_top_at_rebuild_starts[region])); - G1RemSetTrackingPolicy* tracker = _g1h->policy()->remset_tracker(); - if (tracker->needs_scan_for_rebuild(r)) { - _top_at_rebuild_starts[region] = r->top(); - } else { - // Leave TARS at null. - } + _top_at_rebuild_starts[region] = r->top(); } inline void G1CMTask::update_liveness(oop const obj, const size_t obj_size) { diff --git a/src/hotspot/share/gc/g1/g1RemSetTrackingPolicy.cpp b/src/hotspot/share/gc/g1/g1RemSetTrackingPolicy.cpp index 6a97deacfab..21b188c00c2 100644 --- a/src/hotspot/share/gc/g1/g1RemSetTrackingPolicy.cpp +++ b/src/hotspot/share/gc/g1/g1RemSetTrackingPolicy.cpp @@ -30,14 +30,6 @@ #include "gc/g1/g1RemSetTrackingPolicy.hpp" #include "runtime/safepoint.hpp" -bool G1RemSetTrackingPolicy::needs_scan_for_rebuild(G1HeapRegion* r) const { - // All non-free and non-young regions need to be scanned for references; - // At every gc we gather references to other regions in young. - // Free regions trivially do not need scanning because they do not contain live - // objects. - return !(r->is_young() || r->is_free()); -} - void G1RemSetTrackingPolicy::update_at_allocate(G1HeapRegion* r) { assert(r->is_young() || r->is_humongous() || r->is_old(), "Region %u with unexpected heap region type %s", r->hrm_index(), r->get_type_str()); diff --git a/src/hotspot/share/gc/g1/g1RemSetTrackingPolicy.hpp b/src/hotspot/share/gc/g1/g1RemSetTrackingPolicy.hpp index 7560b726080..a89c24e6f4e 100644 --- a/src/hotspot/share/gc/g1/g1RemSetTrackingPolicy.hpp +++ b/src/hotspot/share/gc/g1/g1RemSetTrackingPolicy.hpp @@ -34,9 +34,6 @@ // set is complete. class G1RemSetTrackingPolicy : public CHeapObj { public: - // Do we need to scan the given region to get all outgoing references for remembered - // set rebuild? - bool needs_scan_for_rebuild(G1HeapRegion* r) const; // Update remembered set tracking state at allocation of the region. May be // called at any time. The caller makes sure that the changes to the remembered // set state are visible to other threads. diff --git a/src/hotspot/share/gc/parallel/parallel_globals.hpp b/src/hotspot/share/gc/parallel/parallel_globals.hpp index 291dd5d73c6..e3b9660b069 100644 --- a/src/hotspot/share/gc/parallel/parallel_globals.hpp +++ b/src/hotspot/share/gc/parallel/parallel_globals.hpp @@ -36,10 +36,6 @@ "any dead space)") \ range(0, max_uintx) \ \ - product(uintx, HeapFirstMaximumCompactionCount, 3, \ - "The collection count for the first maximum compaction") \ - range(0, max_uintx) \ - \ product(bool, UseMaximumCompactionOnSystemGC, true, \ "Use maximum compaction in the Parallel Old garbage collector " \ "for a system GC") \ diff --git a/src/hotspot/share/gc/parallel/psParallelCompact.cpp b/src/hotspot/share/gc/parallel/psParallelCompact.cpp index e0d174dcc6a..6c085034384 100644 --- a/src/hotspot/share/gc/parallel/psParallelCompact.cpp +++ b/src/hotspot/share/gc/parallel/psParallelCompact.cpp @@ -837,8 +837,7 @@ bool PSParallelCompact::reassess_maximum_compaction(bool maximum_compaction, const uint total_invocations = ParallelScavengeHeap::heap()->total_full_collections(); assert(total_invocations >= _maximum_compaction_gc_num, "sanity"); const size_t gcs_since_max = total_invocations - _maximum_compaction_gc_num; - const bool is_interval_ended = gcs_since_max > HeapMaximumCompactionInterval - || total_invocations == HeapFirstMaximumCompactionCount; + const bool is_interval_ended = gcs_since_max > HeapMaximumCompactionInterval; // If all regions in old-gen are full const bool is_region_full = diff --git a/src/hotspot/share/gc/serial/tenuredGeneration.cpp b/src/hotspot/share/gc/serial/tenuredGeneration.cpp index fedded6de08..1cddce2dc51 100644 --- a/src/hotspot/share/gc/serial/tenuredGeneration.cpp +++ b/src/hotspot/share/gc/serial/tenuredGeneration.cpp @@ -32,6 +32,7 @@ #include "gc/shared/gcLocker.hpp" #include "gc/shared/gcTimer.hpp" #include "gc/shared/gcTrace.hpp" +#include "gc/shared/genArguments.hpp" #include "gc/shared/space.hpp" #include "gc/shared/spaceDecorator.hpp" #include "logging/log.hpp" diff --git a/src/hotspot/share/gc/shared/gc_globals.hpp b/src/hotspot/share/gc/shared/gc_globals.hpp index 1440f788e18..66496544b96 100644 --- a/src/hotspot/share/gc/shared/gc_globals.hpp +++ b/src/hotspot/share/gc/shared/gc_globals.hpp @@ -540,10 +540,6 @@ "Soft limit for maximum heap size (in bytes)") \ constraint(SoftMaxHeapSizeConstraintFunc,AfterMemoryInit) \ \ - product(size_t, OldSize, ScaleForWordSize(4*M), \ - "(Deprecated) Initial tenured generation size (in bytes)") \ - range(0, max_uintx) \ - \ product(size_t, NewSize, ScaleForWordSize(1*M), \ "Initial new generation size (in bytes)") \ constraint(NewSizeConstraintFunc,AfterErgo) \ diff --git a/src/hotspot/share/gc/shared/genArguments.cpp b/src/hotspot/share/gc/shared/genArguments.cpp index 3eac2e39f38..76f9f6d4052 100644 --- a/src/hotspot/share/gc/shared/genArguments.cpp +++ b/src/hotspot/share/gc/shared/genArguments.cpp @@ -37,6 +37,8 @@ size_t MinNewSize = 0; size_t MinOldSize = 0; size_t MaxOldSize = 0; +size_t OldSize = 0; + size_t GenAlignment = 0; size_t GenArguments::conservative_max_heap_alignment() { return (size_t)Generation::GenGrain; } @@ -152,24 +154,7 @@ void GenArguments::initialize_heap_flags_and_sizes() { vm_exit_during_initialization("Invalid young gen ratio specified"); } - if (OldSize < old_gen_size_lower_bound()) { - FLAG_SET_ERGO(OldSize, old_gen_size_lower_bound()); - } - if (!is_aligned(OldSize, GenAlignment)) { - FLAG_SET_ERGO(OldSize, align_down(OldSize, GenAlignment)); - } - - if (FLAG_IS_CMDLINE(OldSize) && FLAG_IS_DEFAULT(MaxHeapSize)) { - // NewRatio will be used later to set the young generation size so we use - // it to calculate how big the heap should be based on the requested OldSize - // and NewRatio. - assert(NewRatio > 0, "NewRatio should have been set up earlier"); - size_t calculated_heapsize = (OldSize / NewRatio) * (NewRatio + 1); - - calculated_heapsize = align_up(calculated_heapsize, HeapAlignment); - FLAG_SET_ERGO(MaxHeapSize, calculated_heapsize); - FLAG_SET_ERGO(InitialHeapSize, calculated_heapsize); - } + OldSize = old_gen_size_lower_bound(); // Adjust NewSize and OldSize or MaxHeapSize to match each other if (NewSize + OldSize > MaxHeapSize) { @@ -185,23 +170,12 @@ void GenArguments::initialize_heap_flags_and_sizes() { // HeapAlignment, and we just made sure that NewSize is aligned to // GenAlignment. In initialize_flags() we verified that HeapAlignment // is a multiple of GenAlignment. - FLAG_SET_ERGO(OldSize, MaxHeapSize - NewSize); + OldSize = MaxHeapSize - NewSize; } else { FLAG_SET_ERGO(MaxHeapSize, align_up(NewSize + OldSize, HeapAlignment)); } } - // Update NewSize, if possible, to avoid sizing the young gen too small when only - // OldSize is set on the command line. - if (FLAG_IS_CMDLINE(OldSize) && !FLAG_IS_CMDLINE(NewSize)) { - if (OldSize < InitialHeapSize) { - size_t new_size = InitialHeapSize - OldSize; - if (new_size >= MinNewSize && new_size <= MaxNewSize) { - FLAG_SET_ERGO(NewSize, new_size); - } - } - } - DEBUG_ONLY(assert_flags();) } @@ -215,12 +189,6 @@ void GenArguments::initialize_heap_flags_and_sizes() { // In the absence of explicitly set command line flags, policies // such as the use of NewRatio are used to size the generation. -// Minimum sizes of the generations may be different than -// the initial sizes. An inconsistency is permitted here -// in the total size that can be specified explicitly by -// command line specification of OldSize and NewSize and -// also a command line specification of -Xms. Issue a warning -// but allow the values to pass. void GenArguments::initialize_size_info() { GCArguments::initialize_size_info(); @@ -286,37 +254,7 @@ void GenArguments::initialize_size_info() { InitialHeapSize - initial_young_size, MinHeapSize - MinNewSize); - size_t initial_old_size = OldSize; - - // If no explicit command line flag has been set for the - // old generation size, use what is left. - if (!FLAG_IS_CMDLINE(OldSize)) { - // The user has not specified any value but the ergonomics - // may have chosen a value (which may or may not be consistent - // with the overall heap size). In either case make - // the minimum, maximum and initial sizes consistent - // with the young sizes and the overall heap sizes. - initial_old_size = clamp(InitialHeapSize - initial_young_size, MinOldSize, MaxOldSize); - // MaxOldSize and MinOldSize have already been made consistent above. - } else { - // OldSize has been explicitly set on the command line. Use it - // for the initial size but make sure the minimum allow a young - // generation to fit as well. - // If the user has explicitly set an OldSize that is inconsistent - // with other command line flags, issue a warning. - // The generation minimums and the overall heap minimum should - // be within one generation alignment. - if (initial_old_size > MaxOldSize) { - log_warning(gc, ergo)("Inconsistency between maximum heap size and maximum " - "generation sizes: using maximum heap = " SIZE_FORMAT - ", -XX:OldSize flag is being ignored", - MaxHeapSize); - initial_old_size = MaxOldSize; - } else if (initial_old_size < MinOldSize) { - log_warning(gc, ergo)("Inconsistency between initial old size and minimum old size"); - MinOldSize = initial_old_size; - } - } + size_t initial_old_size = clamp(InitialHeapSize - initial_young_size, MinOldSize, MaxOldSize);; // The initial generation sizes should match the initial heap size, // if not issue a warning and resize the generations. This behavior @@ -359,7 +297,7 @@ void GenArguments::initialize_size_info() { } if (OldSize != initial_old_size) { - FLAG_SET_ERGO(OldSize, initial_old_size); + OldSize = initial_old_size; } log_trace(gc, heap)("Minimum old " SIZE_FORMAT " Initial old " SIZE_FORMAT " Maximum old " SIZE_FORMAT, diff --git a/src/hotspot/share/gc/shared/genArguments.hpp b/src/hotspot/share/gc/shared/genArguments.hpp index a4c62ff5afe..dfc392cac6b 100644 --- a/src/hotspot/share/gc/shared/genArguments.hpp +++ b/src/hotspot/share/gc/shared/genArguments.hpp @@ -33,6 +33,8 @@ extern size_t MinNewSize; extern size_t MinOldSize; extern size_t MaxOldSize; +extern size_t OldSize; + extern size_t GenAlignment; class GenArguments : public GCArguments { diff --git a/src/hotspot/share/gc/shenandoah/c2/shenandoahSupport.cpp b/src/hotspot/share/gc/shenandoah/c2/shenandoahSupport.cpp index dbb45995698..8cd76dd3d6b 100644 --- a/src/hotspot/share/gc/shenandoah/c2/shenandoahSupport.cpp +++ b/src/hotspot/share/gc/shenandoah/c2/shenandoahSupport.cpp @@ -1333,6 +1333,14 @@ void ShenandoahBarrierC2Support::pin_and_expand(PhaseIdealLoop* phase) { OuterStripMinedLoopNode* outer = head->as_OuterStripMinedLoop(); hide_strip_mined_loop(outer, outer->unique_ctrl_out()->as_CountedLoop(), phase); } + if (head->is_BaseCountedLoop() && ctrl->is_IfProj() && ctrl->in(0)->is_BaseCountedLoopEnd() && + head->as_BaseCountedLoop()->loopexit() == ctrl->in(0)) { + Node* entry = head->in(LoopNode::EntryControl); + Node* backedge = head->in(LoopNode::LoopBackControl); + Node* new_head = new LoopNode(entry, backedge); + phase->register_control(new_head, phase->get_loop(entry), entry); + phase->lazy_replace(head, new_head); + } } // Expand load-reference-barriers diff --git a/src/hotspot/share/gc/z/zDirector.cpp b/src/hotspot/share/gc/z/zDirector.cpp index 47e24063ead..162e05f2c07 100644 --- a/src/hotspot/share/gc/z/zDirector.cpp +++ b/src/hotspot/share/gc/z/zDirector.cpp @@ -524,6 +524,10 @@ static bool rule_major_allocation_rate(const ZDirectorStats& stats) { } static double calculate_young_to_old_worker_ratio(const ZDirectorStats& stats) { + if (!stats._old_stats._cycle._is_time_trustable) { + return 1.0; + } + const double young_gc_time = gc_time(stats._young_stats); const double old_gc_time = gc_time(stats._old_stats); const size_t reclaimed_per_young_gc = stats._young_stats._stat_heap._reclaimed_avg; diff --git a/src/hotspot/share/interpreter/interpreterRuntime.cpp b/src/hotspot/share/interpreter/interpreterRuntime.cpp index dc5f2f5b637..fb779b039f4 100644 --- a/src/hotspot/share/interpreter/interpreterRuntime.cpp +++ b/src/hotspot/share/interpreter/interpreterRuntime.cpp @@ -645,27 +645,31 @@ JRT_END // void InterpreterRuntime::resolve_get_put(JavaThread* current, Bytecodes::Code bytecode) { - // resolve field - fieldDescriptor info; LastFrameAccessor last_frame(current); constantPoolHandle pool(current, last_frame.method()->constants()); methodHandle m(current, last_frame.method()); + + resolve_get_put(bytecode, last_frame.get_index_u2(bytecode), m, pool, true /*initialize_holder*/, current); +} + +void InterpreterRuntime::resolve_get_put(Bytecodes::Code bytecode, int field_index, + methodHandle& m, + constantPoolHandle& pool, + bool initialize_holder, TRAPS) { + fieldDescriptor info; bool is_put = (bytecode == Bytecodes::_putfield || bytecode == Bytecodes::_nofast_putfield || bytecode == Bytecodes::_putstatic); bool is_static = (bytecode == Bytecodes::_getstatic || bytecode == Bytecodes::_putstatic); - int field_index = last_frame.get_index_u2(bytecode); { - JvmtiHideSingleStepping jhss(current); - JavaThread* THREAD = current; // For exception macros. + JvmtiHideSingleStepping jhss(THREAD); LinkResolver::resolve_field_access(info, pool, field_index, - m, bytecode, CHECK); + m, bytecode, initialize_holder, CHECK); } // end JvmtiHideSingleStepping // check if link resolution caused cpCache to be updated if (pool->resolved_field_entry_at(field_index)->is_resolved(bytecode)) return; - // compute auxiliary field attributes TosState state = as_TosState(info.field_type()); diff --git a/src/hotspot/share/interpreter/interpreterRuntime.hpp b/src/hotspot/share/interpreter/interpreterRuntime.hpp index 297585d37e8..3a8db1363df 100644 --- a/src/hotspot/share/interpreter/interpreterRuntime.hpp +++ b/src/hotspot/share/interpreter/interpreterRuntime.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 @@ -91,7 +91,12 @@ class InterpreterRuntime: AllStatic { static void throw_pending_exception(JavaThread* current); static void resolve_from_cache(JavaThread* current, Bytecodes::Code bytecode); - private: + + // Used by ClassListParser. + static void resolve_get_put(Bytecodes::Code bytecode, int field_index, + methodHandle& m, constantPoolHandle& pool, bool initialize_holder, TRAPS); + +private: // Statics & fields static void resolve_get_put(JavaThread* current, Bytecodes::Code bytecode); diff --git a/src/hotspot/share/interpreter/linkResolver.cpp b/src/hotspot/share/interpreter/linkResolver.cpp index 9b80550130f..1fe715f9757 100644 --- a/src/hotspot/share/interpreter/linkResolver.cpp +++ b/src/hotspot/share/interpreter/linkResolver.cpp @@ -24,6 +24,7 @@ #include "precompiled.hpp" #include "cds/archiveUtils.hpp" +#include "classfile/classLoader.hpp" #include "classfile/defaultMethods.hpp" #include "classfile/javaClasses.hpp" #include "classfile/systemDictionary.hpp" @@ -55,7 +56,8 @@ #include "runtime/fieldDescriptor.inline.hpp" #include "runtime/frame.inline.hpp" #include "runtime/handles.inline.hpp" -#include "runtime/javaThread.hpp" +#include "runtime/javaThread.inline.hpp" +#include "runtime/perfData.hpp" #include "runtime/reflection.hpp" #include "runtime/safepointVerifiers.hpp" #include "runtime/sharedRuntime.hpp" @@ -972,9 +974,14 @@ void LinkResolver::check_field_accessability(Klass* ref_klass, } } -void LinkResolver::resolve_field_access(fieldDescriptor& fd, const constantPoolHandle& pool, int index, const methodHandle& method, Bytecodes::Code byte, TRAPS) { +void LinkResolver::resolve_field_access(fieldDescriptor& fd, + const constantPoolHandle& pool, + int index, + const methodHandle& method, + Bytecodes::Code byte, + bool initialize_class, TRAPS) { LinkInfo link_info(pool, index, method, byte, CHECK); - resolve_field(fd, link_info, byte, true, CHECK); + resolve_field(fd, link_info, byte, initialize_class, CHECK); } void LinkResolver::resolve_field(fieldDescriptor& fd, @@ -1728,6 +1735,10 @@ bool LinkResolver::resolve_previously_linked_invokehandle(CallInfo& result, cons } void LinkResolver::resolve_invokehandle(CallInfo& result, const constantPoolHandle& pool, int index, TRAPS) { + + PerfTraceTimedEvent timer(ClassLoader::perf_resolve_invokehandle_time(), + ClassLoader::perf_resolve_invokehandle_count()); + LinkInfo link_info(pool, index, Bytecodes::_invokehandle, CHECK); if (log_is_enabled(Info, methodhandles)) { ResourceMark rm(THREAD); @@ -1779,6 +1790,9 @@ void LinkResolver::resolve_handle_call(CallInfo& result, } void LinkResolver::resolve_invokedynamic(CallInfo& result, const constantPoolHandle& pool, int indy_index, TRAPS) { + PerfTraceTimedEvent timer(ClassLoader::perf_resolve_invokedynamic_time(), + ClassLoader::perf_resolve_invokedynamic_count()); + int pool_index = pool->resolved_indy_entry_at(indy_index)->constant_pool_index(); // Resolve the bootstrap specifier (BSM + optional arguments). diff --git a/src/hotspot/share/interpreter/linkResolver.hpp b/src/hotspot/share/interpreter/linkResolver.hpp index e18749cd6a5..80f5d230052 100644 --- a/src/hotspot/share/interpreter/linkResolver.hpp +++ b/src/hotspot/share/interpreter/linkResolver.hpp @@ -293,7 +293,16 @@ class LinkResolver: AllStatic { const constantPoolHandle& pool, int index, const methodHandle& method, - Bytecodes::Code byte, TRAPS); + Bytecodes::Code byte, + bool initialize_class, TRAPS); + static void resolve_field_access(fieldDescriptor& result, + const constantPoolHandle& pool, + int index, + const methodHandle& method, + Bytecodes::Code byte, TRAPS) { + resolve_field_access(result, pool, index, method, byte, + /* initialize_class*/true, THREAD); + } static void resolve_field(fieldDescriptor& result, const LinkInfo& link_info, Bytecodes::Code access_kind, bool initialize_class, TRAPS); diff --git a/src/hotspot/share/jfr/jni/jfrJniMethod.cpp b/src/hotspot/share/jfr/jni/jfrJniMethod.cpp index 8953976cd0c..d60baa3c1cc 100644 --- a/src/hotspot/share/jfr/jni/jfrJniMethod.cpp +++ b/src/hotspot/share/jfr/jni/jfrJniMethod.cpp @@ -30,6 +30,7 @@ #include "jfr/recorder/jfrRecorder.hpp" #include "jfr/recorder/checkpoint/jfrMetadataEvent.hpp" #include "jfr/recorder/checkpoint/types/traceid/jfrTraceId.inline.hpp" +#include "jfr/recorder/repository/jfrChunk.hpp" #include "jfr/recorder/repository/jfrRepository.hpp" #include "jfr/recorder/repository/jfrChunkRotation.hpp" #include "jfr/recorder/repository/jfrChunkWriter.hpp" @@ -425,3 +426,7 @@ JVM_END JVM_ENTRY_NO_ENV(void, jfr_unregister_stack_filter(JNIEnv* env, jclass jvm, jlong id)) JfrStackFilterRegistry::remove(id); JVM_END + +NO_TRANSITION(jlong, jfr_nanos_now(JNIEnv* env, jclass jvm)) + return JfrChunk::nanos_now(); +NO_TRANSITION_END diff --git a/src/hotspot/share/jfr/jni/jfrJniMethod.hpp b/src/hotspot/share/jfr/jni/jfrJniMethod.hpp index 6a2d622d7e9..ca119c1f8c3 100644 --- a/src/hotspot/share/jfr/jni/jfrJniMethod.hpp +++ b/src/hotspot/share/jfr/jni/jfrJniMethod.hpp @@ -165,6 +165,8 @@ jlong JNICALL jfr_register_stack_filter(JNIEnv* env, jclass jvm, jobjectArray cl jlong JNICALL jfr_unregister_stack_filter(JNIEnv* env, jclass jvm, jlong id); +jlong JNICALL jfr_nanos_now(JNIEnv* env, jclass jvm); + #ifdef __cplusplus } #endif diff --git a/src/hotspot/share/jfr/jni/jfrJniMethodRegistration.cpp b/src/hotspot/share/jfr/jni/jfrJniMethodRegistration.cpp index 7ef831f6282..415c7468a62 100644 --- a/src/hotspot/share/jfr/jni/jfrJniMethodRegistration.cpp +++ b/src/hotspot/share/jfr/jni/jfrJniMethodRegistration.cpp @@ -100,7 +100,8 @@ JfrJniMethodRegistration::JfrJniMethodRegistration(JNIEnv* env) { (char*)"hostTotalSwapMemory", (char*)"()J", (void*) jfr_host_total_swap_memory, (char*)"emitDataLoss", (char*)"(J)V", (void*)jfr_emit_data_loss, (char*)"registerStackFilter", (char*)"([Ljava/lang/String;[Ljava/lang/String;)J", (void*)jfr_register_stack_filter, - (char*)"unregisterStackFilter", (char*)"(J)V", (void*)jfr_unregister_stack_filter + (char*)"unregisterStackFilter", (char*)"(J)V", (void*)jfr_unregister_stack_filter, + (char*)"nanosNow", (char*)"()J", (void*)jfr_nanos_now }; const size_t method_array_length = sizeof(method) / sizeof(JNINativeMethod); diff --git a/src/hotspot/share/jfr/leakprofiler/checkpoint/objectSampleCheckpoint.cpp b/src/hotspot/share/jfr/leakprofiler/checkpoint/objectSampleCheckpoint.cpp index 2412454f6aa..b7f9b733c0f 100644 --- a/src/hotspot/share/jfr/leakprofiler/checkpoint/objectSampleCheckpoint.cpp +++ b/src/hotspot/share/jfr/leakprofiler/checkpoint/objectSampleCheckpoint.cpp @@ -36,6 +36,7 @@ #include "jfr/recorder/checkpoint/types/traceid/jfrTraceId.inline.hpp" #include "jfr/recorder/service/jfrOptionSet.hpp" #include "jfr/recorder/stacktrace/jfrStackTraceRepository.hpp" +#include "jfr/recorder/storage/jfrReferenceCountedStorage.hpp" #include "jfr/support/jfrKlassUnloading.hpp" #include "jfr/support/jfrMethodLookup.hpp" #include "jfr/utilities/jfrHashtable.hpp" @@ -272,11 +273,30 @@ static void install_stack_traces(const ObjectSampler* sampler) { iterate_samples(installer); } +// Resets the blob write states from the previous epoch. +static void reset_blob_write_state(const ObjectSampler* sampler, JavaThread* jt) { + assert(sampler != nullptr, "invariant"); + const ObjectSample* sample = sampler->last_resolved(); + while (sample != nullptr) { + if (sample->has_stacktrace()) { + sample->stacktrace()->reset_write_state(); + } + if (sample->has_thread()) { + sample->thread()->reset_write_state(); + } + if (sample->has_type_set()) { + sample->type_set()->reset_write_state(); + } + sample = sample->next(); + } +} + void ObjectSampleCheckpoint::on_rotation(const ObjectSampler* sampler) { assert(sampler != nullptr, "invariant"); assert(LeakProfiler::is_running(), "invariant"); JavaThread* const thread = JavaThread::current(); DEBUG_ONLY(JfrJavaSupport::check_java_thread_in_native(thread);) + reset_blob_write_state(sampler, thread); if (!ObjectSampler::has_unresolved_entry()) { return; } @@ -326,38 +346,34 @@ void ObjectSampleCheckpoint::write_stacktrace(const JfrStackTrace* trace, JfrChe } } -static void write_blob(const JfrBlobHandle& blob, JfrCheckpointWriter& writer, bool reset) { - if (reset) { - blob->reset_write_state(); - return; - } +static void write_blob(const JfrBlobHandle& blob, JfrCheckpointWriter& writer) { blob->exclusive_write(writer); } -static void write_type_set_blob(const ObjectSample* sample, JfrCheckpointWriter& writer, bool reset) { +static void write_type_set_blob(const ObjectSample* sample, JfrCheckpointWriter& writer) { if (sample->has_type_set()) { - write_blob(sample->type_set(), writer, reset); + write_blob(sample->type_set(), writer); } } -static void write_thread_blob(const ObjectSample* sample, JfrCheckpointWriter& writer, bool reset) { +static void write_thread_blob(const ObjectSample* sample, JfrCheckpointWriter& writer) { assert(sample->has_thread(), "invariant"); if (sample->is_virtual_thread() || has_thread_exited(sample->thread_id())) { - write_blob(sample->thread(), writer, reset); + write_blob(sample->thread(), writer); } } -static void write_stacktrace_blob(const ObjectSample* sample, JfrCheckpointWriter& writer, bool reset) { +static void write_stacktrace_blob(const ObjectSample* sample, JfrCheckpointWriter& writer) { if (sample->has_stacktrace()) { - write_blob(sample->stacktrace(), writer, reset); + write_blob(sample->stacktrace(), writer); } } -static void write_blobs(const ObjectSample* sample, JfrCheckpointWriter& writer, bool reset) { +static void write_blobs(const ObjectSample* sample, JfrCheckpointWriter& writer) { assert(sample != nullptr, "invariant"); - write_stacktrace_blob(sample, writer, reset); - write_thread_blob(sample, writer, reset); - write_type_set_blob(sample, writer, reset); + write_stacktrace_blob(sample, writer); + write_thread_blob(sample, writer); + write_type_set_blob(sample, writer); } class BlobWriter { @@ -365,18 +381,14 @@ class BlobWriter { const ObjectSampler* _sampler; JfrCheckpointWriter& _writer; const jlong _last_sweep; - bool _reset; public: BlobWriter(const ObjectSampler* sampler, JfrCheckpointWriter& writer, jlong last_sweep) : - _sampler(sampler), _writer(writer), _last_sweep(last_sweep), _reset(false) {} + _sampler(sampler), _writer(writer), _last_sweep(last_sweep) {} void sample_do(ObjectSample* sample) { if (sample->is_alive_and_older_than(_last_sweep)) { - write_blobs(sample, _writer, _reset); + write_blobs(sample, _writer); } } - void set_reset() { - _reset = true; - } }; static void write_sample_blobs(const ObjectSampler* sampler, bool emit_all, Thread* thread) { @@ -385,9 +397,6 @@ static void write_sample_blobs(const ObjectSampler* sampler, bool emit_all, Thre JfrCheckpointWriter writer(thread, false); BlobWriter cbw(sampler, writer, last_sweep); iterate_samples(cbw, true); - // reset blob write states - cbw.set_reset(); - iterate_samples(cbw, true); } void ObjectSampleCheckpoint::write(const ObjectSampler* sampler, EdgeStore* edge_store, bool emit_all, Thread* thread) { @@ -403,67 +412,17 @@ void ObjectSampleCheckpoint::write(const ObjectSampler* sampler, EdgeStore* edge } } -// A linked list of saved type set blobs for the epoch. -// The link consist of a reference counted handle. -static JfrBlobHandle saved_type_set_blobs; - -static void release_state_for_previous_epoch() { - // decrements the reference count and the list is reinitialized - saved_type_set_blobs = JfrBlobHandle(); -} - -class BlobInstaller { - public: - ~BlobInstaller() { - release_state_for_previous_epoch(); - } - void sample_do(ObjectSample* sample) { - if (!sample->is_dead()) { - sample->set_type_set(saved_type_set_blobs); - } - } -}; - -static void install_type_set_blobs() { - if (saved_type_set_blobs.valid()) { - BlobInstaller installer; - iterate_samples(installer); - } -} - -static void save_type_set_blob(JfrCheckpointWriter& writer) { - assert(writer.has_data(), "invariant"); - const JfrBlobHandle blob = writer.copy(); - if (saved_type_set_blobs.valid()) { - saved_type_set_blobs->set_next(blob); - } else { - saved_type_set_blobs = blob; - } -} - // This routine has exclusive access to the sampler instance on entry. -void ObjectSampleCheckpoint::on_type_set(JfrCheckpointWriter& writer) { +void ObjectSampleCheckpoint::on_type_set(JavaThread* jt) { assert(LeakProfiler::is_running(), "invariant"); DEBUG_ONLY(JfrJavaSupport::check_java_thread_in_vm(JavaThread::current());) assert(ClassLoaderDataGraph_lock->owned_by_self(), "invariant"); if (!ObjectSampler::has_unresolved_entry()) { return; } - const ObjectSample* const last = ObjectSampler::sampler()->last(); + ObjectSample* const last = ObjectSampler::sampler()->last(); assert(last != nullptr, "invariant"); assert(last != ObjectSampler::sampler()->last_resolved(), "invariant"); - if (writer.has_data()) { - save_type_set_blob(writer); - } - install_type_set_blobs(); + JfrReferenceCountedStorage::install(last, ObjectSampler::sampler()->last_resolved()); ObjectSampler::sampler()->set_last_resolved(last); } - -// This routine does NOT have exclusive access to the sampler instance on entry. -void ObjectSampleCheckpoint::on_type_set_unload(JfrCheckpointWriter& writer) { - assert(LeakProfiler::is_running(), "invariant"); - assert_locked_or_safepoint(ClassLoaderDataGraph_lock); - if (writer.has_data() && ObjectSampler::has_unresolved_entry()) { - save_type_set_blob(writer); - } -} diff --git a/src/hotspot/share/jfr/leakprofiler/checkpoint/objectSampleCheckpoint.hpp b/src/hotspot/share/jfr/leakprofiler/checkpoint/objectSampleCheckpoint.hpp index eb887f2db9a..bf1965b13b5 100644 --- a/src/hotspot/share/jfr/leakprofiler/checkpoint/objectSampleCheckpoint.hpp +++ b/src/hotspot/share/jfr/leakprofiler/checkpoint/objectSampleCheckpoint.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 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,8 +50,7 @@ class ObjectSampleCheckpoint : AllStatic { static void write(const ObjectSampler* sampler, EdgeStore* edge_store, bool emit_all, Thread* thread); static void clear(); public: - static void on_type_set(JfrCheckpointWriter& writer); - static void on_type_set_unload(JfrCheckpointWriter& writer); + static void on_type_set(JavaThread* jt); static void on_thread_exit(traceid tid); static void on_rotation(const ObjectSampler* sampler); }; diff --git a/src/hotspot/share/jfr/leakprofiler/sampling/objectSample.hpp b/src/hotspot/share/jfr/leakprofiler/sampling/objectSample.hpp index c202ba8d8aa..214de827d03 100644 --- a/src/hotspot/share/jfr/leakprofiler/sampling/objectSample.hpp +++ b/src/hotspot/share/jfr/leakprofiler/sampling/objectSample.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 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 @@ -233,7 +233,7 @@ class ObjectSample : public JfrCHeapObj { return _type_set.valid(); } - void set_type_set(const JfrBlobHandle& ref) { + void install_type_set(const JfrBlobHandle& ref) { if (_type_set != ref) { if (_type_set.valid()) { _type_set->set_next(ref); diff --git a/src/hotspot/share/jfr/recorder/checkpoint/jfrCheckpointManager.cpp b/src/hotspot/share/jfr/recorder/checkpoint/jfrCheckpointManager.cpp index 0aed916702e..a2e887e71a3 100644 --- a/src/hotspot/share/jfr/recorder/checkpoint/jfrCheckpointManager.cpp +++ b/src/hotspot/share/jfr/recorder/checkpoint/jfrCheckpointManager.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. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -37,6 +37,7 @@ #include "jfr/recorder/service/jfrOptionSet.hpp" #include "jfr/recorder/storage/jfrEpochStorage.inline.hpp" #include "jfr/recorder/storage/jfrMemorySpace.inline.hpp" +#include "jfr/recorder/storage/jfrReferenceCountedStorage.hpp" #include "jfr/recorder/storage/jfrStorageUtils.inline.hpp" #include "jfr/recorder/stringpool/jfrStringPool.hpp" #include "jfr/support/jfrDeprecationManager.hpp" @@ -589,12 +590,14 @@ void JfrCheckpointManager::clear_type_set() { MutexLocker module_lock(Module_lock); JfrTypeSet::clear(&writer, &leakp_writer); } - JfrDeprecationManager::on_type_set(leakp_writer, nullptr, thread); - // We placed a blob in the Deprecated subsystem by moving the information - // from the leakp writer. For the real writer, the data will not be - // committed, because the JFR system is yet to be started. - // Therefore, the writer is cancelled before its destructor is run, - // to avoid writing unnecessary information into the checkpoint system. + JfrAddRefCountedBlob add_blob(leakp_writer); + JfrDeprecationManager::on_type_set(nullptr, thread); + // We installed a blob in the JfrReferenceCountedStorage subsystem + // by moving the information from the leakp writer. + // For the real writer, the data will not be committed, + // because the JFR system is yet to be started. + // Therefore, we cancel the writer before its destructor is run + // to avoid writing invalid information into the checkpoint system. writer.cancel(); } @@ -613,11 +616,11 @@ void JfrCheckpointManager::write_type_set() { MutexLocker module_lock(thread, Module_lock); JfrTypeSet::serialize(&writer, &leakp_writer, false, false); } + JfrAddRefCountedBlob add_blob(leakp_writer); if (LeakProfiler::is_running()) { - ObjectSampleCheckpoint::on_type_set(leakp_writer); + ObjectSampleCheckpoint::on_type_set(thread); } - // Place this call after ObjectSampleCheckpoint::on_type_set. - JfrDeprecationManager::on_type_set(leakp_writer, _chunkwriter, thread); + JfrDeprecationManager::on_type_set(_chunkwriter, thread); } write(); } @@ -626,10 +629,7 @@ void JfrCheckpointManager::on_unloading_classes() { assert_locked_or_safepoint(ClassLoaderDataGraph_lock); JfrCheckpointWriter writer(Thread::current()); JfrTypeSet::on_unloading_classes(&writer); - if (LeakProfiler::is_running()) { - ObjectSampleCheckpoint::on_type_set_unload(writer); - } - JfrDeprecationManager::on_type_set_unload(writer); + JfrAddRefCountedBlob add_blob(writer, false /* move */, false /* reset */); } static size_t flush_type_set(Thread* thread) { diff --git a/src/hotspot/share/jfr/recorder/checkpoint/jfrCheckpointWriter.hpp b/src/hotspot/share/jfr/recorder/checkpoint/jfrCheckpointWriter.hpp index a8ec2fd70f5..aaebc8f3e7e 100644 --- a/src/hotspot/share/jfr/recorder/checkpoint/jfrCheckpointWriter.hpp +++ b/src/hotspot/share/jfr/recorder/checkpoint/jfrCheckpointWriter.hpp @@ -54,6 +54,7 @@ struct JfrCheckpointContext { }; class JfrCheckpointWriter : public JfrCheckpointWriterBase { + friend class JfrAddRefCountedBlob; friend class JfrCheckpointManager; friend class JfrDeprecationManager; friend class JfrSerializerRegistration; diff --git a/src/hotspot/share/jfr/recorder/repository/jfrChunk.cpp b/src/hotspot/share/jfr/recorder/repository/jfrChunk.cpp index 6d0ec7773b9..b88ba06bdf7 100644 --- a/src/hotspot/share/jfr/recorder/repository/jfrChunk.cpp +++ b/src/hotspot/share/jfr/recorder/repository/jfrChunk.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2023, 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 @@ -35,7 +35,7 @@ static const u2 JFR_VERSION_MAJOR = 2; static const u2 JFR_VERSION_MINOR = 1; // strictly monotone -static jlong nanos_now() { +jlong JfrChunk::nanos_now() { static jlong last = 0; jlong seconds; @@ -147,7 +147,7 @@ void JfrChunk::update_start_ticks() { } void JfrChunk::update_start_nanos() { - const jlong now = nanos_now(); + const jlong now = JfrChunk::nanos_now(); assert(now >= _start_nanos, "invariant"); assert(now >= _last_update_nanos, "invariant"); _start_nanos = _last_update_nanos = now; diff --git a/src/hotspot/share/jfr/recorder/repository/jfrChunk.hpp b/src/hotspot/share/jfr/recorder/repository/jfrChunk.hpp index d7bd3411160..91b42948181 100644 --- a/src/hotspot/share/jfr/recorder/repository/jfrChunk.hpp +++ b/src/hotspot/share/jfr/recorder/repository/jfrChunk.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2019, 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 @@ -34,6 +34,8 @@ const u1 PAD = 0; class JfrChunk : public JfrCHeapObj { friend class JfrChunkWriter; friend class JfrChunkHeadWriter; + public: + static jlong nanos_now(); private: char* _path; int64_t _start_ticks; diff --git a/src/hotspot/share/jfr/recorder/storage/jfrReferenceCountedStorage.cpp b/src/hotspot/share/jfr/recorder/storage/jfrReferenceCountedStorage.cpp new file mode 100644 index 00000000000..ac652905c5b --- /dev/null +++ b/src/hotspot/share/jfr/recorder/storage/jfrReferenceCountedStorage.cpp @@ -0,0 +1,79 @@ +/* + * 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. + * + */ + +#include "precompiled.hpp" +#include "jfr/leakprofiler/sampling/objectSampler.hpp" +#include "jfr/recorder/checkpoint/jfrCheckpointWriter.hpp" +#include "jfr/recorder/storage/jfrReferenceCountedStorage.hpp" +#include "jfr/support/jfrDeprecationManager.hpp" + +// Currently only two subsystems use type set blobs. Save a blob only if either has an unresolved entry. +static inline bool save_blob_predicate() { + return JfrDeprecationManager::has_unresolved_entry() || ObjectSampler::has_unresolved_entry(); +} + +JfrAddRefCountedBlob::JfrAddRefCountedBlob(JfrCheckpointWriter& writer, bool move /* true */, bool reset /* true */) : _reset(reset) { + if (writer.has_data()) { + if (save_blob_predicate()) { + JfrReferenceCountedStorage::save_blob(writer, move); + } else if (move) { + writer.cancel(); + } + } + DEBUG_ONLY(if (reset) JfrReferenceCountedStorage::set_scope();) +} + +JfrAddRefCountedBlob::~JfrAddRefCountedBlob() { + if (_reset) { + JfrReferenceCountedStorage::reset(); + } +} + +JfrBlobHandle JfrReferenceCountedStorage::_type_sets = JfrBlobHandle(); +DEBUG_ONLY(bool JfrReferenceCountedStorage::_scope = false;) + +void JfrReferenceCountedStorage::save_blob(JfrCheckpointWriter& writer, bool move /* false */) { + assert(writer.has_data(), "invariant"); + const JfrBlobHandle blob = move ? writer.move() : writer.copy(); + if (_type_sets.valid()) { + _type_sets->set_next(blob); + return; + } + _type_sets = blob; +} + +void JfrReferenceCountedStorage::reset() { + assert(_scope, "invariant"); + if (_type_sets.valid()) { + _type_sets = JfrBlobHandle(); + } + DEBUG_ONLY(_scope = false;) +} + +#ifdef ASSERT +void JfrReferenceCountedStorage::set_scope() { + assert(!_scope, "invariant"); + _scope = true; +} +#endif diff --git a/src/hotspot/share/jfr/recorder/storage/jfrReferenceCountedStorage.hpp b/src/hotspot/share/jfr/recorder/storage/jfrReferenceCountedStorage.hpp new file mode 100644 index 00000000000..6d32e1b0ead --- /dev/null +++ b/src/hotspot/share/jfr/recorder/storage/jfrReferenceCountedStorage.hpp @@ -0,0 +1,68 @@ +/* + * 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. + * + */ + +#ifndef SHARE_JFR_RECORDER_STORAGE_JFRREFERENCECOUNTEDSTORAGE_HPP +#define SHARE_JFR_RECORDER_STORAGE_JFRREFERENCECOUNTEDSTORAGE_HPP + +#include "jfr/utilities/jfrBlob.hpp" +#include "memory/allocation.hpp" +#include "utilities/macros.hpp" + +class JfrCheckpointWriter; + +// RAII helper class for adding blobs to the storage. +class JfrAddRefCountedBlob : public StackObj { + private: + bool _reset; + public: + JfrAddRefCountedBlob(JfrCheckpointWriter& writer, bool move = true, bool reset = true); + ~JfrAddRefCountedBlob(); +}; + +// The debug aid 'scope' implies the proper RAII save construct is placed on stack. +// This is a necessary condition for installing reference counted storage to nodes. +class JfrReferenceCountedStorage : AllStatic { + friend class JfrAddRefCountedBlob; + private: + static JfrBlobHandle _type_sets; // linked-list of blob handles saved during epoch. + DEBUG_ONLY(static bool _scope;) + + static void save_blob(JfrCheckpointWriter& writer, bool move = false); + static void reset(); + DEBUG_ONLY(static void set_scope();) + + public: + template + static void install(T* node, const T* end) { + assert(_scope, "invariant"); + if (_type_sets.valid()) { + while (node != end) { + node->install_type_set(_type_sets); + node = node->next(); + } + } + } +}; + +#endif // SHARE_JFR_RECORDER_STORAGE_JFRREFERENCECOUNTEDSTORAGE_HPP diff --git a/src/hotspot/share/jfr/support/jfrDeprecationEventWriter.cpp b/src/hotspot/share/jfr/support/jfrDeprecationEventWriter.cpp index 24cdbdc1611..4aa61bdddff 100644 --- a/src/hotspot/share/jfr/support/jfrDeprecationEventWriter.cpp +++ b/src/hotspot/share/jfr/support/jfrDeprecationEventWriter.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. +* Copyright (c) 2023, 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,8 +116,8 @@ bool JfrDeprecatedStackTraceWriter::process(const JfrDeprecatedEdge* edge) { return true; } -JfrDeprecatedEventWriter::JfrDeprecatedEventWriter(JfrChunkWriter& cw, bool stacktrace) : - _now(JfrTicks::now()),_cw(cw), _for_removal(only_for_removal()), _stacktrace(stacktrace), _did_write(false) {} +JfrDeprecatedEventWriter::JfrDeprecatedEventWriter(JfrChunkWriter& cw, JfrCheckpointWriter& tsw, bool stacktrace) : + _now(JfrTicks::now()),_cw(cw), _tsw(tsw), _for_removal(only_for_removal()), _stacktrace(stacktrace) {} static size_t calculate_event_size(const JfrDeprecatedEdge* edge, JfrChunkWriter& cw, const JfrTicks& now, bool stacktrace) { assert(edge != nullptr, "invariant"); @@ -141,14 +141,31 @@ static void write_event(const JfrDeprecatedEdge* edge, JfrChunkWriter& cw, const cw.write(edge->for_removal()); } +static void write_type_set(const JfrDeprecatedEdge* edge, JfrCheckpointWriter& tsw) { + if (!edge->has_type_set()) { + return; + } + edge->type_set()->exclusive_write(tsw); +} + bool JfrDeprecatedEventWriter::process(const JfrDeprecatedEdge* edge) { assert(edge != nullptr, "invariant"); if (_for_removal && !edge->for_removal()) { return true; } - write_event(edge, _cw,_now, _stacktrace); - if (!_did_write) { - _did_write = true; + write_event(edge, _cw, _now, _stacktrace); + write_type_set(edge, _tsw); + return true; +} + +JfrDeprecatedEventClear::JfrDeprecatedEventClear() {} + +bool JfrDeprecatedEventClear::process(const JfrDeprecatedEdge* edge) { + assert(edge != nullptr, "invariant"); + if (!edge->has_type_set()) { + return true; } + edge->type_set()->reset_write_state(); return true; } + diff --git a/src/hotspot/share/jfr/support/jfrDeprecationEventWriter.hpp b/src/hotspot/share/jfr/support/jfrDeprecationEventWriter.hpp index 77699c94979..1d73d4dc3a3 100644 --- a/src/hotspot/share/jfr/support/jfrDeprecationEventWriter.hpp +++ b/src/hotspot/share/jfr/support/jfrDeprecationEventWriter.hpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. +* Copyright (c) 2023, 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 @@ -56,12 +56,17 @@ class JfrDeprecatedEventWriter : public StackObj { private: JfrTicks _now; JfrChunkWriter& _cw; + JfrCheckpointWriter& _tsw; bool _for_removal; bool _stacktrace; - bool _did_write; public: - JfrDeprecatedEventWriter(JfrChunkWriter& cw, bool stacktrace); - bool did_write() const { return _did_write; } + JfrDeprecatedEventWriter(JfrChunkWriter& cw, JfrCheckpointWriter& tsw, bool stacktrace); + bool process(const JfrDeprecatedEdge* edge); +}; + +class JfrDeprecatedEventClear : public StackObj { + public: + JfrDeprecatedEventClear(); bool process(const JfrDeprecatedEdge* edge); }; diff --git a/src/hotspot/share/jfr/support/jfrDeprecationManager.cpp b/src/hotspot/share/jfr/support/jfrDeprecationManager.cpp index 5f5c87d239c..8969b787a1c 100644 --- a/src/hotspot/share/jfr/support/jfrDeprecationManager.cpp +++ b/src/hotspot/share/jfr/support/jfrDeprecationManager.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. +* Copyright (c) 2023, 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,7 @@ #include "jfr/recorder/checkpoint/types/traceid/jfrTraceId.inline.hpp" #include "jfr/recorder/repository/jfrChunkWriter.hpp" #include "jfr/recorder/stacktrace/jfrStackTraceRepository.hpp" +#include "jfr/recorder/storage/jfrReferenceCountedStorage.hpp" #include "jfr/support/jfrDeprecationEventWriter.hpp" #include "jfr/support/jfrDeprecationManager.hpp" #include "jfr/support/jfrKlassUnloading.hpp" @@ -66,6 +67,7 @@ static inline traceid load_traceid(const Method* method) { JfrDeprecatedEdge::JfrDeprecatedEdge(const Method* method, Method* sender, int bci, u1 frame_type, JavaThread* jt) : _invocation_time(JfrTicks::now()), _stacktrace(), + _type_set(), _next(nullptr), _deprecated_ik(method->method_holder()), _deprecated_methodid(load_traceid(method)), @@ -94,11 +96,25 @@ const JfrBlobHandle& JfrDeprecatedEdge::stacktrace() const { return _stacktrace; } +bool JfrDeprecatedEdge::has_type_set() const { + return _type_set.valid(); +} + +const JfrBlobHandle& JfrDeprecatedEdge::type_set() const { + assert(has_type_set(), "invariant"); + return _type_set; +} + +void JfrDeprecatedEdge::install_type_set(const JfrBlobHandle& type_set) { + assert(!has_type_set(), "invariant"); + _type_set = type_set; +} + typedef JfrLinkedList DeprecatedEdgeList; static DeprecatedEdgeList _list; // Newly constructed edges are concurrently added to this list. static DeprecatedEdgeList _pending_list; // During epoch rotation (safepoint) entries in _list are moved onto _pending_list -static DeprecatedEdgeList _resolved_list; // Fully resolved edges (event and stacktrace blobs). +static DeprecatedEdgeList _resolved_list; // Fully resolved edges (event, stacktrace and typeset blobs). static JfrDeprecatedEdge* allocate_edge(const Method* method, Method* sender, int bci, u1 frame_type, JavaThread* jt) { DEBUG_ONLY(JfrJavaSupport::check_java_thread_in_vm(jt);) @@ -225,10 +241,6 @@ static void transfer_list() { } } -void JfrDeprecationManager::on_level_setting_update(int64_t new_level) { - JfrDeprecatedEventWriterState::on_level_setting_update(new_level); -} - void JfrDeprecationManager::on_safepoint_clear() { assert(!_enqueue_klasses, "invariant"); // We are now starting JFR, so begin enqueuing tagged klasses. @@ -270,6 +282,23 @@ static void add_to_leakp_set(const JfrDeprecatedEdge* edge) { static DeprecatedEdgeList::NodePtr _pending_head = nullptr; static DeprecatedEdgeList::NodePtr _pending_tail = nullptr; +inline DeprecatedEdgeList::NodePtr pending_head() { + return Atomic::load(&_pending_head); +} + +// The test for a pending head can be read concurrently from a thread doing class unloading. +inline static bool has_pending_head() { + return pending_head() != nullptr; +} + +inline static bool no_pending_head() { + return !has_pending_head(); +} + +inline static void set_pending_head(DeprecatedEdgeList::NodePtr head) { + Atomic::store(&_pending_head, head); +} + class PendingListProcessor { private: JfrCheckpointWriter& _writer; @@ -281,66 +310,57 @@ class PendingListProcessor { JfrDeprecatedStackTraceWriter::install_stacktrace_blob(edge, _writer, _jt); assert(edge->has_stacktrace(), "invariant"); add_to_leakp_set(edge); - if (_pending_head == nullptr) { - _pending_head = edge; + if (no_pending_head()) { + set_pending_head(edge); } _pending_tail = edge; return true; } }; -void JfrDeprecationManager::prepare_type_set(JavaThread* jt) { - _pending_head = nullptr; +// Resets the pending head and tail. +// Resets blob write states for nodes on the resolved list, dirtied in the previous epoch. +static void reset_type_set_blobs() { + set_pending_head(nullptr); _pending_tail = nullptr; + if (_resolved_list.is_nonempty()) { + JfrDeprecatedEventClear clear; + _resolved_list.iterate(clear); + } +} + +void JfrDeprecationManager::prepare_type_set(JavaThread* jt) { + reset_type_set_blobs(); if (_pending_list.is_nonempty()) { JfrKlassUnloading::sort(true); JfrCheckpointWriter writer(true /* prev epoch */, jt, false /* header */); PendingListProcessor plp(writer, jt); _pending_list.iterate(plp); - assert(_pending_head != nullptr, "invariant"); + assert(has_pending_head(), "invariant"); assert(_pending_tail != nullptr, "invariant"); assert(_pending_tail->next() == nullptr, "invariant"); // Excise already resolved edges to link them. _pending_tail->set_next(_resolved_list.cut()); // Re-insertion. - _resolved_list.add_list(_pending_head); + _resolved_list.add_list(pending_head()); _pending_list.clear(); } assert(_pending_list.is_empty(), "invariant"); } -// A linked-list of blob handles. -static JfrBlobHandle type_set_blobs; - -static inline void write_type_set_blobs(JfrCheckpointWriter& writer) { - type_set_blobs->write(writer); -} - -static void save_type_set_blob(JfrCheckpointWriter& writer, bool copy = false) { - assert(writer.has_data(), "invariant"); - const JfrBlobHandle blob = copy ? writer.copy() : writer.move(); - if (type_set_blobs.valid()) { - type_set_blobs->set_next(blob); - } else { - type_set_blobs = blob; - } -} - -void JfrDeprecationManager::on_type_set_unload(JfrCheckpointWriter& writer) { - if (writer.has_data()) { - save_type_set_blob(writer, true); - } +bool JfrDeprecationManager::has_unresolved_entry() { + return _list.is_nonempty() || has_pending_head() || _pending_list.is_nonempty(); } static inline bool has_stacktrace() { return JfrEventSetting::has_stacktrace(JfrDeprecatedInvocationEvent); } -static inline bool write_events(JfrChunkWriter& cw) { +static inline void write_events(JfrChunkWriter& cw, Thread* thread, bool on_error) { assert(_resolved_list.is_nonempty(), "invariant"); - JfrDeprecatedEventWriter ebw(cw, has_stacktrace()); + JfrCheckpointWriter type_set_writer(!on_error, thread, false); + JfrDeprecatedEventWriter ebw(cw, type_set_writer, has_stacktrace()); _resolved_list.iterate(ebw); - return ebw.did_write(); } static inline void write_stacktraces(JfrChunkWriter& cw) { @@ -349,34 +369,30 @@ static inline void write_stacktraces(JfrChunkWriter& cw) { _resolved_list.iterate(scw); } -static inline void write_type_sets(Thread* thread, bool on_error) { - JfrCheckpointWriter writer(!on_error, thread, false); - write_type_set_blobs(writer); -} - -// First, we consolidate all stacktrace blobs into a single TYPE_STACKTRACE checkpoint and serialize it to the chunk. -// Secondly, we serialize all events to the chunk. -// Thirdly, the type set blobs are written into the JfrCheckpoint system, to be serialized to the chunk -// just after we return from here. +// First, we consolidate all stack trace blobs into a single TYPE_STACKTRACE checkpoint +// and serialize it to the chunk. Then, all events are serialized, and unique type set blobs +// written into the JfrCheckpoint system to be serialized to the chunk upon return. void JfrDeprecationManager::write_edges(JfrChunkWriter& cw, Thread* thread, bool on_error /* false */) { if (_resolved_list.is_nonempty() && JfrEventSetting::is_enabled(JfrDeprecatedInvocationEvent)) { if (has_stacktrace()) { write_stacktraces(cw); } - if (write_events(cw)) { - write_type_sets(thread, on_error); - } + write_events(cw, thread, on_error); } } -void JfrDeprecationManager::on_type_set(JfrCheckpointWriter& writer, JfrChunkWriter* cw, Thread* thread) { +void JfrDeprecationManager::on_type_set(JfrChunkWriter* cw, Thread* thread) { assert(_pending_list.is_empty(), "invariant"); - if (_pending_head != nullptr) { - save_type_set_blob(writer); - } else { - writer.cancel(); + if (has_pending_head()) { + assert(_pending_tail != nullptr, "invariant"); + // Install type set blobs for the pending, i.e. unresolved nodes. + JfrReferenceCountedStorage::install(pending_head(), _pending_tail->next()); } if (cw != nullptr) { write_edges(*cw, thread); } } + +void JfrDeprecationManager::on_level_setting_update(int64_t new_level) { + JfrDeprecatedEventWriterState::on_level_setting_update(new_level); +} diff --git a/src/hotspot/share/jfr/support/jfrDeprecationManager.hpp b/src/hotspot/share/jfr/support/jfrDeprecationManager.hpp index a1948357491..d156679521a 100644 --- a/src/hotspot/share/jfr/support/jfrDeprecationManager.hpp +++ b/src/hotspot/share/jfr/support/jfrDeprecationManager.hpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. +* Copyright (c) 2023, 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 @@ -42,6 +42,7 @@ class JfrDeprecatedEdge : public CHeapObj { private: JfrTicks _invocation_time; JfrBlobHandle _stacktrace; + JfrBlobHandle _type_set; JfrDeprecatedEdge* _next; InstanceKlass* _deprecated_ik; traceid _deprecated_methodid; @@ -58,7 +59,7 @@ class JfrDeprecatedEdge : public CHeapObj { public: JfrDeprecatedEdge(const Method* method, Method* sender, int bci, u1 frame_type, JavaThread* jt); - const JfrDeprecatedEdge* next() const { return _next; } + JfrDeprecatedEdge* next() const { return _next; } void set_next(JfrDeprecatedEdge* edge) { _next = edge; } bool has_event() const; @@ -68,6 +69,10 @@ class JfrDeprecatedEdge : public CHeapObj { const JfrBlobHandle& stacktrace() const; void install_stacktrace_blob(JavaThread* jt); + bool has_type_set() const; + const JfrBlobHandle& type_set() const; + void install_type_set(const JfrBlobHandle& type_set); + const InstanceKlass* deprecated_ik() const { return _deprecated_ik; } traceid deprecated_methodid() const { return _deprecated_methodid; } @@ -89,11 +94,11 @@ class JfrDeprecationManager : AllStatic { static void on_safepoint_write(); static void on_recorder_stop(); static void prepare_type_set(JavaThread* jt); - static void on_type_set(JfrCheckpointWriter& writer, JfrChunkWriter* cw, Thread* thread); - static void on_type_set_unload(JfrCheckpointWriter& writer); + static void on_type_set(JfrChunkWriter* cw, Thread* thread); static void write_edges(JfrChunkWriter& cw, Thread* thread, bool on_error = false); static void on_link(const Method* method, Method* sender, int bci, u1 frame_type, JavaThread* thread); static void on_level_setting_update(int64_t new_level); + static bool has_unresolved_entry(); }; #endif // SHARE_JFR_SUPPORT_JFRDEPRECATIONMANAGER_HPP diff --git a/src/hotspot/share/jfr/support/jfrIntrinsics.hpp b/src/hotspot/share/jfr/support/jfrIntrinsics.hpp index 65a94164a97..6520f3cb00c 100644 --- a/src/hotspot/share/jfr/support/jfrIntrinsics.hpp +++ b/src/hotspot/share/jfr/support/jfrIntrinsics.hpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2012, 2023, 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 @@ -47,13 +47,13 @@ class JfrIntrinsicSupport : AllStatic { #define JFR_HAVE_INTRINSICS #define JFR_TEMPLATES(template) \ + template(jdk_jfr_internal_HiddenWait, "jdk/jfr/internal/HiddenWait") \ template(jdk_jfr_internal_JVM, "jdk/jfr/internal/JVM") \ template(jdk_jfr_internal_event_EventWriterFactory, "jdk/jfr/internal/event/EventWriterFactory") \ template(jdk_jfr_internal_event_EventConfiguration_signature, "Ljdk/jfr/internal/event/EventConfiguration;") \ template(getEventWriter_signature, "()Ljdk/jfr/internal/event/EventWriter;") \ template(eventConfiguration_name, "eventConfiguration") \ template(commit_name, "commit") \ - template(jfr_chunk_rotation_monitor, "jdk/jfr/internal/JVM$ChunkRotationMonitor") \ #define JFR_INTRINSICS(do_intrinsic, do_class, do_name, do_signature, do_alias) \ do_intrinsic(_counterTime, jdk_jfr_internal_JVM, counterTime_name, void_long_signature, F_SN) \ diff --git a/src/hotspot/share/jvmci/jvmciCompiler.cpp b/src/hotspot/share/jvmci/jvmciCompiler.cpp index 5be065aadad..2b8684f7ab8 100644 --- a/src/hotspot/share/jvmci/jvmciCompiler.cpp +++ b/src/hotspot/share/jvmci/jvmciCompiler.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2023, 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 @@ -200,6 +200,15 @@ void JVMCICompiler::print_timers() { _hosted_code_installs.print_on(tty, " Install Code: "); } +bool JVMCICompiler::is_intrinsic_supported(const methodHandle& method) { + vmIntrinsics::ID id = method->intrinsic_id(); + assert(id != vmIntrinsics::_none, "must be a VM intrinsic"); + JavaThread* thread = JavaThread::current(); + JVMCIEnv jvmciEnv(thread, __FILE__, __LINE__); + JVMCIRuntime* runtime = JVMCI::compiler_runtime(thread, false); + return runtime->is_intrinsic_supported(&jvmciEnv, (jint) id); +} + void JVMCICompiler::CodeInstallStats::print_on(outputStream* st, const char* prefix) const { double time = _timer.seconds(); st->print_cr("%s%7.3f s (installs: %d, CodeBlob total size: %d, CodeBlob code size: %d)", diff --git a/src/hotspot/share/jvmci/jvmciCompiler.hpp b/src/hotspot/share/jvmci/jvmciCompiler.hpp index bdb50cfc26a..0d03bd08bf6 100644 --- a/src/hotspot/share/jvmci/jvmciCompiler.hpp +++ b/src/hotspot/share/jvmci/jvmciCompiler.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2023, 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 @@ -136,6 +136,8 @@ class JVMCICompiler : public AbstractCompiler { // Print compilation timers and statistics virtual void print_timers(); + virtual bool is_intrinsic_supported(const methodHandle& method); + // Gets the number of methods that have been successfully compiled by // a call to JVMCICompiler::compile_method(). int methods_compiled() { return _methods_compiled; } diff --git a/src/hotspot/share/jvmci/jvmciEnv.cpp b/src/hotspot/share/jvmci/jvmciEnv.cpp index 624b25b9e2c..01b78b45b2a 100644 --- a/src/hotspot/share/jvmci/jvmciEnv.cpp +++ b/src/hotspot/share/jvmci/jvmciEnv.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. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -967,6 +967,31 @@ jboolean JVMCIEnv::call_HotSpotJVMCIRuntime_isGCSupported (JVMCIObject runtime, } } +jboolean JVMCIEnv::call_HotSpotJVMCIRuntime_isIntrinsicSupported (JVMCIObject runtime, jint intrinsicIdentifier) { + JavaThread* THREAD = JavaThread::current(); // For exception macros. + if (is_hotspot()) { + JavaCallArguments jargs; + jargs.push_oop(Handle(THREAD, HotSpotJVMCI::resolve(runtime))); + jargs.push_int(intrinsicIdentifier); + JavaValue result(T_BOOLEAN); + JavaCalls::call_special(&result, + HotSpotJVMCI::HotSpotJVMCIRuntime::klass(), + vmSymbols::isIntrinsicSupported_name(), + vmSymbols::int_bool_signature(), &jargs, CHECK_0); + return result.get_jboolean(); + } else { + JNIAccessMark jni(this, THREAD); + jboolean result = jni()->CallNonvirtualBooleanMethod(runtime.as_jobject(), + JNIJVMCI::HotSpotJVMCIRuntime::clazz(), + JNIJVMCI::HotSpotJVMCIRuntime::isIntrinsicSupported_method(), + intrinsicIdentifier); + if (jni()->ExceptionCheck()) { + return false; + } + return result; + } +} + JVMCIObject JVMCIEnv::call_HotSpotJVMCIRuntime_compileMethod (JVMCIObject runtime, JVMCIObject method, int entry_bci, jlong compile_state, int id) { JavaThread* THREAD = JVMCI::compilation_tick(JavaThread::current()); // For exception macros. diff --git a/src/hotspot/share/jvmci/jvmciEnv.hpp b/src/hotspot/share/jvmci/jvmciEnv.hpp index b3aa487f34c..69f6647b0d6 100644 --- a/src/hotspot/share/jvmci/jvmciEnv.hpp +++ b/src/hotspot/share/jvmci/jvmciEnv.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 @@ -359,6 +359,8 @@ class JVMCIEnv : public ResourceObj { jboolean call_HotSpotJVMCIRuntime_isGCSupported(JVMCIObject runtime, jint gcIdentifier); + jboolean call_HotSpotJVMCIRuntime_isIntrinsicSupported(JVMCIObject runtime, jint intrinsicIdentifier); + void call_HotSpotJVMCIRuntime_postTranslation(JVMCIObject object, JVMCI_TRAPS); // Converts the JavaKind.typeChar value in `ch` to a BasicType diff --git a/src/hotspot/share/jvmci/jvmciJavaClasses.hpp b/src/hotspot/share/jvmci/jvmciJavaClasses.hpp index 3561093caaa..d5fcd2aaaba 100644 --- a/src/hotspot/share/jvmci/jvmciJavaClasses.hpp +++ b/src/hotspot/share/jvmci/jvmciJavaClasses.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2023, 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 @@ -202,6 +202,7 @@ objectarray_field(HotSpotJVMCIRuntime, excludeFromJVMCICompilation, "[Ljava/lang/Module;") \ jvmci_method(CallNonvirtualObjectMethod, GetMethodID, call_special, JVMCIObject, HotSpotJVMCIRuntime, compileMethod, compileMethod_signature) \ jvmci_method(CallNonvirtualObjectMethod, GetMethodID, call_special, JVMCIObject, HotSpotJVMCIRuntime, isGCSupported, int_bool_signature) \ + jvmci_method(CallNonvirtualObjectMethod, GetMethodID, call_special, JVMCIObject, HotSpotJVMCIRuntime, isIntrinsicSupported, int_bool_signature) \ jvmci_method(CallNonvirtualVoidMethod, GetMethodID, call_special, void, HotSpotJVMCIRuntime, bootstrapFinished, void_method_signature) \ jvmci_method(CallNonvirtualVoidMethod, GetMethodID, call_special, void, HotSpotJVMCIRuntime, shutdown, void_method_signature) \ jvmci_method(CallStaticObjectMethod, GetStaticMethodID, call_static, JVMCIObject, HotSpotJVMCIRuntime, runtime, runtime_signature) \ diff --git a/src/hotspot/share/jvmci/jvmciRuntime.cpp b/src/hotspot/share/jvmci/jvmciRuntime.cpp index 9dc0e381df9..504fbfcb1b0 100644 --- a/src/hotspot/share/jvmci/jvmciRuntime.cpp +++ b/src/hotspot/share/jvmci/jvmciRuntime.cpp @@ -2050,6 +2050,16 @@ bool JVMCIRuntime::is_gc_supported(JVMCIEnv* JVMCIENV, CollectedHeap::Name name) return JVMCIENV->call_HotSpotJVMCIRuntime_isGCSupported(receiver, (int) name); } +bool JVMCIRuntime::is_intrinsic_supported(JVMCIEnv* JVMCIENV, jint id) { + JVMCI_EXCEPTION_CONTEXT + + JVMCIObject receiver = get_HotSpotJVMCIRuntime(JVMCIENV); + if (JVMCIENV->has_pending_exception()) { + fatal_exception(JVMCIENV, "Exception during HotSpotJVMCIRuntime initialization"); + } + return JVMCIENV->call_HotSpotJVMCIRuntime_isIntrinsicSupported(receiver, id); +} + // ------------------------------------------------------------------ JVMCI::CodeInstallResult JVMCIRuntime::register_method(JVMCIEnv* JVMCIENV, const methodHandle& method, diff --git a/src/hotspot/share/jvmci/jvmciRuntime.hpp b/src/hotspot/share/jvmci/jvmciRuntime.hpp index 123cfde15ac..bc5bee4edeb 100644 --- a/src/hotspot/share/jvmci/jvmciRuntime.hpp +++ b/src/hotspot/share/jvmci/jvmciRuntime.hpp @@ -431,6 +431,9 @@ class JVMCIRuntime: public CHeapObj { // Determines if the GC identified by `name` is supported by the JVMCI compiler. bool is_gc_supported(JVMCIEnv* JVMCIENV, CollectedHeap::Name name); + // Determines if the intrinsic identified by `id` is supported by the JVMCI compiler. + bool is_intrinsic_supported(JVMCIEnv* JVMCIENV, jint id); + // Register the result of a compilation. JVMCI::CodeInstallResult register_method(JVMCIEnv* JVMCIENV, const methodHandle& target, diff --git a/src/hotspot/share/jvmci/vmSymbols_jvmci.hpp b/src/hotspot/share/jvmci/vmSymbols_jvmci.hpp index 13d81d9f286..c0a7afe2b63 100644 --- a/src/hotspot/share/jvmci/vmSymbols_jvmci.hpp +++ b/src/hotspot/share/jvmci/vmSymbols_jvmci.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2023, 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 @@ -81,6 +81,7 @@ template(compileMethod_name, "compileMethod") \ template(compileMethod_signature, "(Ljdk/vm/ci/hotspot/HotSpotResolvedJavaMethod;IJI)Ljdk/vm/ci/hotspot/HotSpotCompilationRequestResult;") \ template(isGCSupported_name, "isGCSupported") \ + template(isIntrinsicSupported_name, "isIntrinsicSupported") \ template(fromMetaspace_name, "fromMetaspace") \ template(method_fromMetaspace_signature, "(JLjdk/vm/ci/hotspot/HotSpotResolvedObjectTypeImpl;)Ljdk/vm/ci/hotspot/HotSpotResolvedJavaMethod;") \ template(constantPool_fromMetaspace_signature, "(J)Ljdk/vm/ci/hotspot/HotSpotConstantPool;") \ diff --git a/src/hotspot/share/logging/logTag.hpp b/src/hotspot/share/logging/logTag.hpp index eb289957b5c..fd55d5fda8f 100644 --- a/src/hotspot/share/logging/logTag.hpp +++ b/src/hotspot/share/logging/logTag.hpp @@ -110,6 +110,7 @@ class outputStream; LOG_TAG(jvmti) \ LOG_TAG(lambda) \ LOG_TAG(library) \ + LOG_TAG(link) \ LOG_TAG(liveness) \ LOG_TAG(load) /* Trace all classes loaded */ \ LOG_TAG(loader) \ diff --git a/src/hotspot/share/nmt/memMapPrinter.cpp b/src/hotspot/share/nmt/memMapPrinter.cpp index ec5003c562e..5480904d57c 100644 --- a/src/hotspot/share/nmt/memMapPrinter.cpp +++ b/src/hotspot/share/nmt/memMapPrinter.cpp @@ -30,16 +30,17 @@ #include "logging/logAsyncWriter.hpp" #include "gc/shared/collectedHeap.hpp" #include "memory/universe.hpp" +#include "memory/resourceArea.hpp" #include "nmt/memflags.hpp" +#include "nmt/memFlagBitmap.hpp" +#include "nmt/memMapPrinter.hpp" +#include "nmt/memTracker.hpp" +#include "nmt/virtualMemoryTracker.hpp" #include "runtime/nonJavaThread.hpp" #include "runtime/osThread.hpp" #include "runtime/thread.hpp" #include "runtime/threadSMR.hpp" #include "runtime/vmThread.hpp" -#include "nmt/memFlagBitmap.hpp" -#include "nmt/memMapPrinter.hpp" -#include "nmt/memTracker.hpp" -#include "nmt/virtualMemoryTracker.hpp" #include "utilities/globalDefinitions.hpp" #include "utilities/growableArray.hpp" #include "utilities/ostream.hpp" @@ -203,6 +204,8 @@ static void print_thread_details(uintx thread_id, const char* name, outputStream // Given a region [from, to), if it intersects a known thread stack, print detail infos about that thread. static void print_thread_details_for_supposed_stack_address(const void* from, const void* to, outputStream* st) { + ResourceMark rm; + #define HANDLE_THREAD(T) \ if (T != nullptr && vma_touches_thread_stack(from, to, T)) { \ print_thread_details((uintx)(T->osthread()->thread_id()), T->name(), st); \ diff --git a/src/hotspot/share/oops/constantPool.cpp b/src/hotspot/share/oops/constantPool.cpp index 85cc5a77f1e..35a1d95b6e6 100644 --- a/src/hotspot/share/oops/constantPool.cpp +++ b/src/hotspot/share/oops/constantPool.cpp @@ -29,6 +29,7 @@ #include "cds/cdsConfig.hpp" #include "cds/classPrelinker.hpp" #include "cds/heapShared.hpp" +#include "classfile/classLoader.hpp" #include "classfile/classLoaderData.hpp" #include "classfile/javaClasses.inline.hpp" #include "classfile/metadataOnStackMark.hpp" @@ -62,7 +63,8 @@ #include "runtime/handles.inline.hpp" #include "runtime/init.hpp" #include "runtime/javaCalls.hpp" -#include "runtime/javaThread.hpp" +#include "runtime/javaThread.inline.hpp" +#include "runtime/perfData.hpp" #include "runtime/signature.hpp" #include "runtime/vframe.inline.hpp" #include "utilities/checkedCast.hpp" @@ -298,20 +300,22 @@ objArrayOop ConstantPool::prepare_resolved_references_for_archiving() { objArrayOop rr = resolved_references(); if (rr != nullptr) { - ConstantPool* orig_pool = ArchiveBuilder::current()->get_source_addr(this); - objArrayOop scratch_rr = HeapShared::scratch_resolved_references(orig_pool); + int rr_len = rr->length(); + ConstantPool* src_cp = ArchiveBuilder::current()->get_source_addr(this); + objArrayOop scratch_rr = HeapShared::scratch_resolved_references(src_cp); Array* ref_map = reference_map(); int ref_map_len = ref_map == nullptr ? 0 : ref_map->length(); - int rr_len = rr->length(); for (int i = 0; i < rr_len; i++) { oop obj = rr->obj_at(i); scratch_rr->obj_at_put(i, nullptr); - if (obj != nullptr && i < ref_map_len) { - int index = object_to_cp_index(i); - if (tag_at(index).is_string()) { - assert(java_lang_String::is_instance(obj), "must be"); - if (!ArchiveHeapWriter::is_string_too_large_to_archive(obj)) { - scratch_rr->obj_at_put(i, obj); + if (obj != nullptr) { + if (i < ref_map_len) { + int index = object_to_cp_index(i); + if (tag_at(index).is_string()) { + assert(java_lang_String::is_instance(obj), "must be"); + if (!ArchiveHeapWriter::is_string_too_large_to_archive(obj)) { + scratch_rr->obj_at_put(i, obj); + } } } } @@ -384,23 +388,61 @@ void ConstantPool::remove_unshareable_info() { // we always set _on_stack to true to avoid having to change _flags during runtime. _flags |= (_on_stack | _is_shared); - if (!_pool_holder->is_linked() && !_pool_holder->verified_at_dump_time()) { - return; + // resolved_references(): remember its length. If it cannot be restored + // from the archived heap objects at run time, we need to dynamically allocate it. + if (cache() != nullptr) { + set_resolved_reference_length( + resolved_references() != nullptr ? resolved_references()->length() : 0); + set_resolved_references(OopHandle()); + } + remove_unshareable_entries(); +} + +static const char* get_type(Klass* k) { + const char* type; + Klass* src_k; + if (ArchiveBuilder::is_active() && ArchiveBuilder::current()->is_in_buffer_space(k)) { + src_k = ArchiveBuilder::current()->get_source_addr(k); + } else { + src_k = k; + } + + if (src_k->is_objArray_klass()) { + src_k = ObjArrayKlass::cast(src_k)->bottom_klass(); + assert(!src_k->is_objArray_klass(), "sanity"); + } + + if (src_k->is_typeArray_klass()) { + type = "prim"; + } else { + InstanceKlass* src_ik = InstanceKlass::cast(src_k); + oop loader = src_ik->class_loader(); + if (loader == nullptr) { + type = "boot"; + } else if (loader == SystemDictionary::java_platform_loader()) { + type = "plat"; + } else if (loader == SystemDictionary::java_system_loader()) { + type = "app"; + } else { + type = "unreg"; + } } - // Resolved references are not in the shared archive. - // Save the length for restoration. It is not necessarily the same length - // as reference_map.length() if invokedynamic is saved. It is needed when - // re-creating the resolved reference array if archived heap data cannot be map - // at runtime. - set_resolved_reference_length( - resolved_references() != nullptr ? resolved_references()->length() : 0); - set_resolved_references(OopHandle()); - bool archived = false; + return type; +} + +void ConstantPool::remove_unshareable_entries() { + ResourceMark rm; + log_info(cds, resolve)("Archiving CP entries for %s", pool_holder()->name()->as_C_string()); for (int cp_index = 1; cp_index < length(); cp_index++) { // cp_index 0 is unused - switch (tag_at(cp_index).value()) { + int cp_tag = tag_at(cp_index).value(); + switch (cp_tag) { + case JVM_CONSTANT_UnresolvedClass: + ArchiveBuilder::alloc_stats()->record_klass_cp_entry(false, false); + break; case JVM_CONSTANT_UnresolvedClassInError: tag_at_put(cp_index, JVM_CONSTANT_UnresolvedClass); + ArchiveBuilder::alloc_stats()->record_klass_cp_entry(false, true); break; case JVM_CONSTANT_MethodHandleInError: tag_at_put(cp_index, JVM_CONSTANT_MethodHandle); @@ -412,8 +454,9 @@ void ConstantPool::remove_unshareable_info() { tag_at_put(cp_index, JVM_CONSTANT_Dynamic); break; case JVM_CONSTANT_Class: - archived = maybe_archive_resolved_klass_at(cp_index); - ArchiveBuilder::alloc_stats()->record_klass_cp_entry(archived); + remove_resolved_klass_if_non_deterministic(cp_index); + break; + default: break; } } @@ -424,40 +467,46 @@ void ConstantPool::remove_unshareable_info() { } } -bool ConstantPool::maybe_archive_resolved_klass_at(int cp_index) { +void ConstantPool::remove_resolved_klass_if_non_deterministic(int cp_index) { assert(ArchiveBuilder::current()->is_in_buffer_space(this), "must be"); assert(tag_at(cp_index).is_klass(), "must be resolved"); - if (pool_holder()->is_hidden() && cp_index == pool_holder()->this_class_index()) { - // All references to a hidden class's own field/methods are through this - // index, which was resolved in ClassFileParser::fill_instance_klass. We - // must preserve it. - return true; + Klass* k = resolved_klass_at(cp_index); + bool can_archive; + + if (k == nullptr) { + // We'd come here if the referenced class has been excluded via + // SystemDictionaryShared::is_excluded_class(). As a result, ArchiveBuilder + // has cleared the resolved_klasses()->at(...) pointer to NULL. Thus, we + // need to revert the tag to JVM_CONSTANT_UnresolvedClass. + can_archive = false; + } else { + ConstantPool* src_cp = ArchiveBuilder::current()->get_source_addr(this); + can_archive = ClassPrelinker::is_resolution_deterministic(src_cp, cp_index); } - CPKlassSlot kslot = klass_slot_at(cp_index); - int resolved_klass_index = kslot.resolved_klass_index(); - Klass* k = resolved_klasses()->at(resolved_klass_index); - // k could be null if the referenced class has been excluded via - // SystemDictionaryShared::is_excluded_class(). + if (!can_archive) { + int resolved_klass_index = klass_slot_at(cp_index).resolved_klass_index(); + resolved_klasses()->at_put(resolved_klass_index, nullptr); + tag_at_put(cp_index, JVM_CONSTANT_UnresolvedClass); + } - if (k != nullptr) { - ConstantPool* src_cp = ArchiveBuilder::current()->get_source_addr(this); - if (ClassPrelinker::can_archive_resolved_klass(src_cp, cp_index)) { - if (log_is_enabled(Debug, cds, resolve)) { - ResourceMark rm; - log_debug(cds, resolve)("Resolved klass CP entry [%d]: %s => %s", cp_index, - pool_holder()->external_name(), k->external_name()); - } - return true; + LogStreamHandle(Trace, cds, resolve) log; + if (log.is_enabled()) { + ResourceMark rm; + log.print("%s klass CP entry [%3d]: %s %s", + (can_archive ? "archived" : "reverted"), + cp_index, pool_holder()->name()->as_C_string(), get_type(pool_holder())); + if (can_archive) { + log.print(" => %s %s%s", k->name()->as_C_string(), get_type(k), + (!k->is_instance_klass() || pool_holder()->is_subtype_of(k)) ? "" : " (not supertype)"); + } else { + Symbol* name = klass_name_at(cp_index); + log.print(" %s", name->as_C_string()); } } - // This referenced class cannot be archived. Revert the tag to UnresolvedClass, - // so that the proper class loading and initialization can happen at runtime. - resolved_klasses()->at_put(resolved_klass_index, nullptr); - tag_at_put(cp_index, JVM_CONSTANT_UnresolvedClass); - return false; + ArchiveBuilder::alloc_stats()->record_klass_cp_entry(can_archive, /*reverted=*/!can_archive); } #endif // INCLUDE_CDS @@ -705,6 +754,31 @@ int ConstantPool::to_cp_index(int index, Bytecodes::Code code) { } } +bool ConstantPool::is_resolved(int index, Bytecodes::Code code) { + assert(cache() != nullptr, "'index' is a rewritten index so this class must have been rewritten"); + switch(code) { + case Bytecodes::_invokedynamic: + return resolved_indy_entry_at(index)->is_resolved(); + + case Bytecodes::_getfield: + case Bytecodes::_getstatic: + case Bytecodes::_putfield: + case Bytecodes::_putstatic: + return resolved_field_entry_at(index)->is_resolved(code); + + case Bytecodes::_invokeinterface: + case Bytecodes::_invokehandle: + case Bytecodes::_invokespecial: + case Bytecodes::_invokestatic: + case Bytecodes::_invokevirtual: + case Bytecodes::_fast_invokevfinal: // Bytecode interpreter uses this + return resolved_method_entry_at(index)->is_resolved(code); + + default: + fatal("Unexpected bytecode: %s", Bytecodes::name(code)); + } +} + u2 ConstantPool::uncached_name_and_type_ref_index_at(int cp_index) { if (tag_at(cp_index).has_bootstrap()) { u2 pool_index = bootstrap_name_and_type_ref_index_at(cp_index); @@ -1014,7 +1088,9 @@ oop ConstantPool::resolve_constant_at_impl(const constantPoolHandle& this_cp, } case JVM_CONSTANT_Dynamic: - { + { PerfTraceTimedEvent timer(ClassLoader::perf_resolve_invokedynamic_time(), + ClassLoader::perf_resolve_invokedynamic_count()); + // Resolve the Dynamically-Computed constant to invoke the BSM in order to obtain the resulting oop. BootstrapInfo bootstrap_specifier(this_cp, cp_index); @@ -1072,7 +1148,9 @@ oop ConstantPool::resolve_constant_at_impl(const constantPoolHandle& this_cp, break; case JVM_CONSTANT_MethodHandle: - { + { PerfTraceTimedEvent timer(ClassLoader::perf_resolve_method_handle_time(), + ClassLoader::perf_resolve_method_handle_count()); + int ref_kind = this_cp->method_handle_ref_kind_at(cp_index); int callee_index = this_cp->method_handle_klass_index_at(cp_index); Symbol* name = this_cp->method_handle_name_ref_at(cp_index); @@ -1120,7 +1198,9 @@ oop ConstantPool::resolve_constant_at_impl(const constantPoolHandle& this_cp, } case JVM_CONSTANT_MethodType: - { + { PerfTraceTimedEvent timer(ClassLoader::perf_resolve_method_type_time(), + ClassLoader::perf_resolve_method_type_count()); + Symbol* signature = this_cp->method_type_signature_at(cp_index); { ResourceMark rm(THREAD); log_debug(class, resolve)("resolve JVM_CONSTANT_MethodType [%d/%d] %s", diff --git a/src/hotspot/share/oops/constantPool.hpp b/src/hotspot/share/oops/constantPool.hpp index e48229749f3..7a17c62ddaf 100644 --- a/src/hotspot/share/oops/constantPool.hpp +++ b/src/hotspot/share/oops/constantPool.hpp @@ -661,6 +661,8 @@ class ConstantPool : public Metadata { int to_cp_index(int which, Bytecodes::Code code); + bool is_resolved(int which, Bytecodes::Code code); + // Lookup for entries consisting of (name_index, signature_index) u2 name_ref_index_at(int cp_index); // == low-order jshort of name_and_type_at(cp_index) u2 signature_ref_index_at(int cp_index); // == high-order jshort of name_and_type_at(cp_index) @@ -677,9 +679,11 @@ class ConstantPool : public Metadata { // CDS support objArrayOop prepare_resolved_references_for_archiving() NOT_CDS_JAVA_HEAP_RETURN_(nullptr); void add_dumped_interned_strings() NOT_CDS_JAVA_HEAP_RETURN; - bool maybe_archive_resolved_klass_at(int cp_index); void remove_unshareable_info(); void restore_unshareable_info(TRAPS); +private: + void remove_unshareable_entries(); + void remove_resolved_klass_if_non_deterministic(int cp_index); #endif private: diff --git a/src/hotspot/share/oops/cpCache.cpp b/src/hotspot/share/oops/cpCache.cpp index 03dbce19f2e..c16ba3b76f7 100644 --- a/src/hotspot/share/oops/cpCache.cpp +++ b/src/hotspot/share/oops/cpCache.cpp @@ -25,6 +25,7 @@ #include "precompiled.hpp" #include "cds/archiveBuilder.hpp" #include "cds/cdsConfig.hpp" +#include "cds/classPrelinker.hpp" #include "cds/heapShared.hpp" #include "classfile/resolutionErrors.hpp" #include "classfile/systemDictionary.hpp" @@ -388,18 +389,14 @@ void ConstantPoolCache::record_gc_epoch() { #if INCLUDE_CDS void ConstantPoolCache::remove_unshareable_info() { assert(CDSConfig::is_dumping_archive(), "sanity"); - // is the copy to be written into the archive. It's in the ArchiveBuilder's "buffer space". - // However, this->_initial_entries was not copied/relocated by the ArchiveBuilder, so it's - // still pointing to the array allocated inside save_for_archive(). + if (_resolved_indy_entries != nullptr) { for (int i = 0; i < _resolved_indy_entries->length(); i++) { resolved_indy_entry_at(i)->remove_unshareable_info(); } } if (_resolved_field_entries != nullptr) { - for (int i = 0; i < _resolved_field_entries->length(); i++) { - resolved_field_entry_at(i)->remove_unshareable_info(); - } + remove_resolved_field_entries_if_non_deterministic(); } if (_resolved_method_entries != nullptr) { for (int i = 0; i < _resolved_method_entries->length(); i++) { @@ -407,6 +404,41 @@ void ConstantPoolCache::remove_unshareable_info() { } } } + +void ConstantPoolCache::remove_resolved_field_entries_if_non_deterministic() { + ConstantPool* cp = constant_pool(); + ConstantPool* src_cp = ArchiveBuilder::current()->get_source_addr(cp); + for (int i = 0; i < _resolved_field_entries->length(); i++) { + ResolvedFieldEntry* rfi = _resolved_field_entries->adr_at(i); + int cp_index = rfi->constant_pool_index(); + bool archived = false; + bool resolved = rfi->is_resolved(Bytecodes::_getfield) || + rfi->is_resolved(Bytecodes::_putfield); + if (resolved && ClassPrelinker::is_resolution_deterministic(src_cp, cp_index)) { + rfi->mark_and_relocate(); + archived = true; + } else { + rfi->remove_unshareable_info(); + } + if (resolved) { + LogStreamHandle(Trace, cds, resolve) log; + if (log.is_enabled()) { + ResourceMark rm; + int klass_cp_index = cp->uncached_klass_ref_index_at(cp_index); + Symbol* klass_name = cp->klass_name_at(klass_cp_index); + Symbol* name = cp->uncached_name_ref_at(cp_index); + Symbol* signature = cp->uncached_signature_ref_at(cp_index); + log.print("%s field CP entry [%3d]: %s %s %s.%s:%s", + (archived ? "archived" : "reverted"), + cp_index, + cp->pool_holder()->name()->as_C_string(), + (archived ? "=>" : " "), + klass_name->as_C_string(), name->as_C_string(), signature->as_C_string()); + } + } + ArchiveBuilder::alloc_stats()->record_field_cp_entry(archived, resolved && !archived); + } +} #endif // INCLUDE_CDS void ConstantPoolCache::deallocate_contents(ClassLoaderData* data) { diff --git a/src/hotspot/share/oops/cpCache.hpp b/src/hotspot/share/oops/cpCache.hpp index f9239835198..1f91b194623 100644 --- a/src/hotspot/share/oops/cpCache.hpp +++ b/src/hotspot/share/oops/cpCache.hpp @@ -193,14 +193,12 @@ class ConstantPoolCache: public MetaspaceObj { #if INCLUDE_CDS void remove_unshareable_info(); - void save_for_archive(TRAPS); #endif public: static int size() { return align_metadata_size(sizeof(ConstantPoolCache) / wordSize); } private: - // Helpers ConstantPool** constant_pool_addr() { return &_constant_pool; } @@ -224,6 +222,10 @@ class ConstantPoolCache: public MetaspaceObj { void dump_cache(); #endif // INCLUDE_JVMTI +#if INCLUDE_CDS + void remove_resolved_field_entries_if_non_deterministic(); +#endif + // RedefineClasses support DEBUG_ONLY(bool on_stack() { return false; }) void deallocate_contents(ClassLoaderData* data); diff --git a/src/hotspot/share/oops/instanceKlass.cpp b/src/hotspot/share/oops/instanceKlass.cpp index d66ce5ce247..8a716c8f9f6 100644 --- a/src/hotspot/share/oops/instanceKlass.cpp +++ b/src/hotspot/share/oops/instanceKlass.cpp @@ -978,6 +978,8 @@ void InstanceKlass::rewrite_class(TRAPS) { // This is outside is_rewritten flag. In case of an exception, it can be // executed more than once. void InstanceKlass::link_methods(TRAPS) { + PerfTraceTime timer(ClassLoader::perf_ik_link_methods_time()); + int len = methods()->length(); for (int i = len-1; i >= 0; i--) { methodHandle m(THREAD, methods()->at(i)); @@ -2555,7 +2557,9 @@ void InstanceKlass::remove_unshareable_info() { init_implementor(); } - constants()->remove_unshareable_info(); + // Call remove_unshareable_info() on other objects that belong to this class, except + // for constants()->remove_unshareable_info(), which is called in a separate pass in + // ArchiveBuilder::make_klasses_shareable(), for (int i = 0; i < methods()->length(); i++) { Method* m = methods()->at(i); diff --git a/src/hotspot/share/oops/method.cpp b/src/hotspot/share/oops/method.cpp index a5b2267cf33..525ec284c08 100644 --- a/src/hotspot/share/oops/method.cpp +++ b/src/hotspot/share/oops/method.cpp @@ -26,6 +26,7 @@ #include "cds/cdsConfig.hpp" #include "cds/cppVtables.hpp" #include "cds/metaspaceShared.hpp" +#include "classfile/classLoader.hpp" #include "classfile/classLoaderDataGraph.hpp" #include "classfile/metadataOnStackMark.hpp" #include "classfile/symbolTable.hpp" @@ -62,12 +63,14 @@ #include "prims/jvmtiExport.hpp" #include "prims/methodHandles.hpp" #include "runtime/atomic.hpp" +#include "runtime/arguments.hpp" #include "runtime/continuationEntry.hpp" #include "runtime/frame.inline.hpp" #include "runtime/handles.inline.hpp" #include "runtime/init.hpp" #include "runtime/java.hpp" #include "runtime/orderAccess.hpp" +#include "runtime/perfData.hpp" #include "runtime/relocator.hpp" #include "runtime/safepointVerifiers.hpp" #include "runtime/sharedRuntime.hpp" @@ -1168,6 +1171,10 @@ void Method::remove_unshareable_flags() { // Called when the method_holder is getting linked. Setup entrypoints so the method // is ready to be called from interpreter, compiler, and vtables. void Method::link_method(const methodHandle& h_method, TRAPS) { + if (log_is_enabled(Info, perf, class, link)) { + ClassLoader::perf_ik_link_methods_count()->inc(); + } + // If the code cache is full, we may reenter this function for the // leftover methods that weren't linked. if (adapter() != nullptr) { @@ -1219,6 +1226,8 @@ void Method::link_method(const methodHandle& h_method, TRAPS) { } address Method::make_adapters(const methodHandle& mh, TRAPS) { + PerfTraceTime timer(ClassLoader::perf_method_adapters_time()); + // Adapters for compiled code are made eagerly here. They are fairly // small (generally < 100 bytes) and quick to make (and cached and shared) // so making them eagerly shouldn't be too expensive. diff --git a/src/hotspot/share/oops/resolvedFieldEntry.cpp b/src/hotspot/share/oops/resolvedFieldEntry.cpp index 779f7676293..83243251306 100644 --- a/src/hotspot/share/oops/resolvedFieldEntry.cpp +++ b/src/hotspot/share/oops/resolvedFieldEntry.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 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 @@ -23,7 +23,8 @@ */ #include "precompiled.hpp" -#include "resolvedFieldEntry.hpp" +#include "cds/archiveBuilder.hpp" +#include "oops/resolvedFieldEntry.hpp" void ResolvedFieldEntry::print_on(outputStream* st) const { st->print_cr("Field Entry:"); @@ -43,8 +44,14 @@ void ResolvedFieldEntry::print_on(outputStream* st) const { st->print_cr(" - Put Bytecode: %s", Bytecodes::name((Bytecodes::Code)put_code())); } +#if INCLUDE_CDS void ResolvedFieldEntry::remove_unshareable_info() { u2 saved_cpool_index = _cpool_index; memset(this, 0, sizeof(*this)); _cpool_index = saved_cpool_index; } + +void ResolvedFieldEntry::mark_and_relocate() { + ArchiveBuilder::current()->mark_and_relocate_to_buffered_addr(&_field_holder); +} +#endif diff --git a/src/hotspot/share/oops/resolvedFieldEntry.hpp b/src/hotspot/share/oops/resolvedFieldEntry.hpp index 7765240926d..c98d5f54d1e 100644 --- a/src/hotspot/share/oops/resolvedFieldEntry.hpp +++ b/src/hotspot/share/oops/resolvedFieldEntry.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 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,6 +55,17 @@ class ResolvedFieldEntry { u1 _flags; // Flags: [0000|00|is_final|is_volatile] u1 _get_code, _put_code; // Get and Put bytecodes of the field + void copy_from(const ResolvedFieldEntry& other) { + _field_holder = other._field_holder; + _field_offset = other._field_offset; + _field_index = other._field_index; + _cpool_index = other._cpool_index; + _tos_state = other._tos_state; + _flags = other._flags; + _get_code = other._get_code; + _put_code = other._put_code; + } + public: ResolvedFieldEntry(u2 cpi) : _field_holder(nullptr), @@ -65,9 +76,19 @@ class ResolvedFieldEntry { _flags(0), _get_code(0), _put_code(0) {} + ResolvedFieldEntry() : ResolvedFieldEntry(0) {} + ResolvedFieldEntry(const ResolvedFieldEntry& other) { + copy_from(other); + } + + ResolvedFieldEntry& operator=(const ResolvedFieldEntry& other) { + copy_from(other); + return *this; + } + // Bit shift to get flags // Note: Only two flags exists at the moment but more could be added enum { @@ -131,7 +152,10 @@ class ResolvedFieldEntry { } // CDS +#if INCLUDE_CDS void remove_unshareable_info(); + void mark_and_relocate(); +#endif // Offsets static ByteSize field_holder_offset() { return byte_offset_of(ResolvedFieldEntry, _field_holder); } diff --git a/src/hotspot/share/oops/resolvedIndyEntry.cpp b/src/hotspot/share/oops/resolvedIndyEntry.cpp index eff543a5448..93ba3d6916c 100644 --- a/src/hotspot/share/oops/resolvedIndyEntry.cpp +++ b/src/hotspot/share/oops/resolvedIndyEntry.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 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 @@ -23,6 +23,7 @@ */ #include "precompiled.hpp" +#include "cds/archiveBuilder.hpp" #include "code/compressedStream.hpp" #include "oops/method.hpp" #include "oops/resolvedIndyEntry.hpp" @@ -37,6 +38,7 @@ bool ResolvedIndyEntry::check_no_old_or_obsolete_entry() { } } +#if INCLUDE_CDS void ResolvedIndyEntry::remove_unshareable_info() { u2 saved_resolved_references_index = _resolved_references_index; u2 saved_cpool_index = _cpool_index; @@ -45,6 +47,12 @@ void ResolvedIndyEntry::remove_unshareable_info() { _cpool_index = saved_cpool_index; } +void ResolvedIndyEntry::mark_and_relocate() { + assert(is_resolved(), "must be"); + ArchiveBuilder::current()->mark_and_relocate_to_buffered_addr(&_method); +} +#endif + void ResolvedIndyEntry::print_on(outputStream* st) const { st->print_cr("Resolved InvokeDynamic Info:"); if (_method != nullptr) { diff --git a/src/hotspot/share/oops/resolvedIndyEntry.hpp b/src/hotspot/share/oops/resolvedIndyEntry.hpp index 25797d338c4..3d7d3893311 100644 --- a/src/hotspot/share/oops/resolvedIndyEntry.hpp +++ b/src/hotspot/share/oops/resolvedIndyEntry.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 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 @@ -129,7 +129,10 @@ class ResolvedIndyEntry { bool check_no_old_or_obsolete_entry(); // CDS +#if INCLUDE_CDS void remove_unshareable_info(); + void mark_and_relocate(); +#endif // Offsets static ByteSize method_offset() { return byte_offset_of(ResolvedIndyEntry, _method); } diff --git a/src/hotspot/share/oops/resolvedMethodEntry.cpp b/src/hotspot/share/oops/resolvedMethodEntry.cpp index 7702dddd9cc..9564dbbcdc4 100644 --- a/src/hotspot/share/oops/resolvedMethodEntry.cpp +++ b/src/hotspot/share/oops/resolvedMethodEntry.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 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 @@ -23,6 +23,7 @@ */ #include "precompiled.hpp" +#include "cds/archiveBuilder.hpp" #include "oops/method.hpp" #include "oops/resolvedMethodEntry.hpp" @@ -50,10 +51,23 @@ void ResolvedMethodEntry::reset_entry() { } } +#if INCLUDE_CDS void ResolvedMethodEntry::remove_unshareable_info() { reset_entry(); } +void ResolvedMethodEntry::mark_and_relocate(ConstantPool* src_cp) { + if (_method == nullptr) { + assert(bytecode2() == Bytecodes::_invokevirtual, ""); + } else { + ArchiveBuilder::current()->mark_and_relocate_to_buffered_addr(&_method); + } + if (bytecode1() == Bytecodes::_invokeinterface) { + ArchiveBuilder::current()->mark_and_relocate_to_buffered_addr(&_entry_specific._interface_klass); + } +} +#endif + void ResolvedMethodEntry::print_on(outputStream* st) const { st->print_cr("Method Entry:"); diff --git a/src/hotspot/share/oops/resolvedMethodEntry.hpp b/src/hotspot/share/oops/resolvedMethodEntry.hpp index f5f7b8fbab5..e8445223600 100644 --- a/src/hotspot/share/oops/resolvedMethodEntry.hpp +++ b/src/hotspot/share/oops/resolvedMethodEntry.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 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 @@ -224,7 +224,10 @@ class ResolvedMethodEntry { void reset_entry(); // CDS +#if INCLUDE_CDS void remove_unshareable_info(); + void mark_and_relocate(ConstantPool* src_cp); +#endif // Offsets static ByteSize klass_offset() { return byte_offset_of(ResolvedMethodEntry, _entry_specific._interface_klass); } diff --git a/src/hotspot/share/opto/loopPredicate.cpp b/src/hotspot/share/opto/loopPredicate.cpp index e23da54f765..bccc01a86dd 100644 --- a/src/hotspot/share/opto/loopPredicate.cpp +++ b/src/hotspot/share/opto/loopPredicate.cpp @@ -373,7 +373,7 @@ IfProjNode* PhaseIdealLoop::clone_assertion_predicate_for_unswitched_loops(IfNod ParsePredicateSuccessProj* parse_predicate_proj) { TemplateAssertionPredicateExpression template_assertion_predicate_expression( template_assertion_predicate->in(1)->as_Opaque4()); - Opaque4Node* cloned_opaque4_node = template_assertion_predicate_expression.clone(parse_predicate_proj, this); + Opaque4Node* cloned_opaque4_node = template_assertion_predicate_expression.clone(parse_predicate_proj->in(0)->in(0), this); IfProjNode* if_proj = create_new_if_for_predicate(parse_predicate_proj, nullptr, reason, template_assertion_predicate->Opcode(), false); _igvn.replace_input_of(if_proj->in(0), 1, cloned_opaque4_node); _igvn.replace_input_of(parse_predicate_proj->in(0), 0, if_proj); diff --git a/src/hotspot/share/opto/loopnode.cpp b/src/hotspot/share/opto/loopnode.cpp index d58be510516..6c16d7cc6a4 100644 --- a/src/hotspot/share/opto/loopnode.cpp +++ b/src/hotspot/share/opto/loopnode.cpp @@ -169,10 +169,10 @@ Node *PhaseIdealLoop::get_early_ctrl_for_expensive(Node *n, Node* earliest) { return earliest; } - while (1) { - Node *next = ctl; - // Moving the node out of a loop on the projection of a If - // confuses loop predication. So once we hit a Loop in a If branch + while (true) { + Node* next = ctl; + // Moving the node out of a loop on the projection of an If + // confuses Loop Predication. So, once we hit a loop in an If branch // that doesn't branch to an UNC, we stop. The code that process // expensive nodes will notice the loop and skip over it to try to // move the node further up. @@ -6081,20 +6081,27 @@ Node* PhaseIdealLoop::get_late_ctrl_with_anti_dep(LoadNode* n, Node* early, Node return LCA; } -// true if CFG node d dominates CFG node n -bool PhaseIdealLoop::is_dominator(Node *d, Node *n) { - if (d == n) +// Is CFG node 'dominator' dominating node 'n'? +bool PhaseIdealLoop::is_dominator(Node* dominator, Node* n) { + if (dominator == n) { return true; - assert(d->is_CFG() && n->is_CFG(), "must have CFG nodes"); - uint dd = dom_depth(d); + } + assert(dominator->is_CFG() && n->is_CFG(), "must have CFG nodes"); + uint dd = dom_depth(dominator); while (dom_depth(n) >= dd) { - if (n == d) + if (n == dominator) { return true; + } n = idom(n); } return false; } +// Is CFG node 'dominator' strictly dominating node 'n'? +bool PhaseIdealLoop::is_strict_dominator(Node* dominator, Node* n) { + return dominator != n && is_dominator(dominator, n); +} + //------------------------------dom_lca_for_get_late_ctrl_internal------------- // Pair-wise LCA with tags. // Tag each index with the node 'tag' currently being processed @@ -6377,31 +6384,16 @@ void PhaseIdealLoop::build_loop_late_post_work(Node *n, bool pinned) { if (least != early) { // Move the node above predicates as far up as possible so a - // following pass of loop predication doesn't hoist a predicate + // following pass of Loop Predication doesn't hoist a predicate // that depends on it above that node. - Node* new_ctrl = least; - for (;;) { - if (!new_ctrl->is_Proj()) { - break; - } - CallStaticJavaNode* call = new_ctrl->as_Proj()->is_uncommon_trap_if_pattern(); - if (call == nullptr) { - break; - } - int req = call->uncommon_trap_request(); - Deoptimization::DeoptReason trap_reason = Deoptimization::trap_request_reason(req); - if (trap_reason != Deoptimization::Reason_loop_limit_check && - trap_reason != Deoptimization::Reason_predicate && - trap_reason != Deoptimization::Reason_profile_predicate) { - break; - } - Node* c = new_ctrl->in(0)->in(0); - if (is_dominator(c, early) && c != early) { + PredicateEntryIterator predicate_iterator(least); + while (predicate_iterator.has_next()) { + Node* next_predicate_entry = predicate_iterator.next_entry(); + if (is_strict_dominator(next_predicate_entry, early)) { break; } - new_ctrl = c; + least = next_predicate_entry; } - least = new_ctrl; } // Try not to place code on a loop entry projection // which can inhibit range check elimination. diff --git a/src/hotspot/share/opto/loopnode.hpp b/src/hotspot/share/opto/loopnode.hpp index 90ef4da4f1e..8d9d4b3e0e5 100644 --- a/src/hotspot/share/opto/loopnode.hpp +++ b/src/hotspot/share/opto/loopnode.hpp @@ -1011,8 +1011,10 @@ class PhaseIdealLoop : public PhaseTransform { assert(n == find_non_split_ctrl(n), "must return legal ctrl" ); return n; } - // true if CFG node d dominates CFG node n - bool is_dominator(Node *d, Node *n); + + bool is_dominator(Node* dominator, Node* n); + bool is_strict_dominator(Node* dominator, Node* n); + // return get_ctrl for a data node and self(n) for a CFG node Node* ctrl_or_self(Node* n) { if (has_ctrl(n)) diff --git a/src/hotspot/share/opto/loopopts.cpp b/src/hotspot/share/opto/loopopts.cpp index b19c71fdd86..23b2edce654 100644 --- a/src/hotspot/share/opto/loopopts.cpp +++ b/src/hotspot/share/opto/loopopts.cpp @@ -4294,7 +4294,7 @@ bool PhaseIdealLoop::duplicate_loop_backedge(IdealLoopTree *loop, Node_List &old } else { wq.push(c->in(0)); } - assert(!is_dominator(c, region) || c == region, "shouldn't go above region"); + assert(!is_strict_dominator(c, region), "shouldn't go above region"); } Node* region_dom = idom(region); diff --git a/src/hotspot/share/opto/parse1.cpp b/src/hotspot/share/opto/parse1.cpp index 4dc3ac37042..3989020451e 100644 --- a/src/hotspot/share/opto/parse1.cpp +++ b/src/hotspot/share/opto/parse1.cpp @@ -1057,16 +1057,6 @@ void Parse::do_exits() { // loading. It could also be due to an error, so mark this method as not compilable because // otherwise this could lead to an infinite compile loop. // In any case, this code path is rarely (and never in my testing) reached. -#ifdef ASSERT - tty->print_cr("# Can't determine return type."); - tty->print_cr("# exit control"); - _exits.control()->dump(2); - tty->print_cr("# ret phi type"); - _gvn.type(ret_phi)->dump(); - tty->print_cr("# ret phi"); - ret_phi->dump(2); -#endif // ASSERT - assert(false, "Can't determine return type."); C->record_method_not_compilable("Can't determine return type."); return; } diff --git a/src/hotspot/share/opto/phasetype.hpp b/src/hotspot/share/opto/phasetype.hpp index 6448b8331cc..7a83f8c7f27 100644 --- a/src/hotspot/share/opto/phasetype.hpp +++ b/src/hotspot/share/opto/phasetype.hpp @@ -68,9 +68,10 @@ flags(AFTER_RANGE_CHECK_ELIMINATION, "After Range Check Elimination") \ flags(BEFORE_PRE_MAIN_POST, "Before Pre/Main/Post Loops") \ flags(AFTER_PRE_MAIN_POST, "After Pre/Main/Post Loops") \ - flags(SUPERWORD1_BEFORE_SCHEDULE, "Superword 1, Before Schedule") \ - flags(SUPERWORD2_BEFORE_OUTPUT, "Superword 2, Before Output") \ - flags(SUPERWORD3_AFTER_OUTPUT, "Superword 3, After Output") \ + flags(AUTO_VECTORIZATION1_BEFORE_APPLY, "AutoVectorization 1, Before Apply") \ + flags(AUTO_VECTORIZATION2_AFTER_REORDER, "AutoVectorization 2, After Apply Memop Reordering") \ + flags(AUTO_VECTORIZATION3_AFTER_ADJUST_LIMIT, "AutoVectorization 3, After Adjusting Pre-Loop Limit") \ + flags(AUTO_VECTORIZATION4_AFTER_APPLY, "AutoVectorization 4, After Apply") \ flags(BEFORE_CLOOPS, "Before CountedLoop") \ flags(AFTER_CLOOPS, "After CountedLoop") \ flags(PHASEIDEAL_BEFORE_EA, "PhaseIdealLoop before EA") \ diff --git a/src/hotspot/share/opto/predicates.cpp b/src/hotspot/share/opto/predicates.cpp index 9f782f34bdb..5b0de2e02d5 100644 --- a/src/hotspot/share/opto/predicates.cpp +++ b/src/hotspot/share/opto/predicates.cpp @@ -32,29 +32,29 @@ // (i.e. not belonging to an Initialized Assertion Predicate anymore) Node* AssertionPredicatesWithHalt::find_entry(Node* start_proj) { Node* entry = start_proj; - while (is_assertion_predicate_success_proj(entry)) { + while (AssertionPredicateWithHalt::is_predicate(entry)) { entry = entry->in(0)->in(0); } return entry; } -bool AssertionPredicatesWithHalt::is_assertion_predicate_success_proj(const Node* predicate_proj) { - if (predicate_proj == nullptr || !predicate_proj->is_IfProj() || !predicate_proj->in(0)->is_If()) { +bool AssertionPredicateWithHalt::is_predicate(const Node* maybe_success_proj) { + if (maybe_success_proj == nullptr || !maybe_success_proj->is_IfProj() || !maybe_success_proj->in(0)->is_If()) { return false; } - return has_assertion_predicate_opaque(predicate_proj) && has_halt(predicate_proj); + return has_assertion_predicate_opaque(maybe_success_proj) && has_halt(maybe_success_proj); } // Check if the If node of `predicate_proj` has an Opaque4 (Template Assertion Predicate) or an // OpaqueInitializedAssertionPredicate (Initialized Assertion Predicate) node as input. -bool AssertionPredicatesWithHalt::has_assertion_predicate_opaque(const Node* predicate_proj) { +bool AssertionPredicateWithHalt::has_assertion_predicate_opaque(const Node* predicate_proj) { IfNode* iff = predicate_proj->in(0)->as_If(); Node* bol = iff->in(1); return bol->is_Opaque4() || bol->is_OpaqueInitializedAssertionPredicate(); } // Check if the other projection (UCT projection) of `success_proj` has a Halt node as output. -bool AssertionPredicatesWithHalt::has_halt(const Node* success_proj) { +bool AssertionPredicateWithHalt::has_halt(const Node* success_proj) { ProjNode* other_proj = success_proj->as_IfProj()->other_if_proj(); return other_proj->outcnt() == 1 && other_proj->unique_out()->Opcode() == Op_Halt; } @@ -72,7 +72,15 @@ ParsePredicateNode* ParsePredicate::init_parse_predicate(Node* parse_predicate_p return nullptr; } -Deoptimization::DeoptReason RuntimePredicate::uncommon_trap_reason(IfProjNode* if_proj) { +bool ParsePredicate::is_predicate(Node* maybe_success_proj) { + if (!maybe_success_proj->is_IfProj()) { + return false; + } + IfNode* if_node = maybe_success_proj->in(0)->as_If(); + return if_node->is_ParsePredicate(); +} + +Deoptimization::DeoptReason RegularPredicateWithUCT::uncommon_trap_reason(IfProjNode* if_proj) { CallStaticJavaNode* uct_call = if_proj->is_uncommon_trap_if_pattern(); if (uct_call == nullptr) { return Deoptimization::Reason_none; @@ -80,8 +88,20 @@ Deoptimization::DeoptReason RuntimePredicate::uncommon_trap_reason(IfProjNode* i return Deoptimization::trap_request_reason(uct_call->uncommon_trap_request()); } -bool RuntimePredicate::is_success_proj(Node* node, Deoptimization::DeoptReason deopt_reason) { - if (may_be_runtime_predicate_if(node)) { +bool RegularPredicateWithUCT::is_predicate(Node* maybe_success_proj) { + if (may_be_predicate_if(maybe_success_proj)) { + IfProjNode* success_proj = maybe_success_proj->as_IfProj(); + const Deoptimization::DeoptReason deopt_reason = uncommon_trap_reason(success_proj); + return (deopt_reason == Deoptimization::Reason_loop_limit_check || + deopt_reason == Deoptimization::Reason_predicate || + deopt_reason == Deoptimization::Reason_profile_predicate); + } else { + return false; + } +} + +bool RegularPredicateWithUCT::is_predicate(Node* node, Deoptimization::DeoptReason deopt_reason) { + if (may_be_predicate_if(node)) { return deopt_reason == uncommon_trap_reason(node->as_IfProj()); } else { return false; @@ -89,7 +109,7 @@ bool RuntimePredicate::is_success_proj(Node* node, Deoptimization::DeoptReason d } // A Runtime Predicate must have an If or a RangeCheck node, while the If should not be a zero trip guard check. -bool RuntimePredicate::may_be_runtime_predicate_if(Node* node) { +bool RegularPredicateWithUCT::may_be_predicate_if(Node* node) { if (node->is_IfProj()) { const IfNode* if_node = node->in(0)->as_If(); const int opcode_if = if_node->Opcode(); @@ -101,6 +121,10 @@ bool RuntimePredicate::may_be_runtime_predicate_if(Node* node) { return false; } +bool RuntimePredicate::is_success_proj(Node* node, Deoptimization::DeoptReason deopt_reason) { + return RegularPredicateWithUCT::is_predicate(node, deopt_reason); +} + ParsePredicateIterator::ParsePredicateIterator(const Predicates& predicates) : _current_index(0) { const PredicateBlock* loop_limit_check_predicate_block = predicates.loop_limit_check_predicate_block(); if (loop_limit_check_predicate_block->has_parse_predicate()) { @@ -356,3 +380,18 @@ bool TemplateAssertionPredicateExpressionNode::is_in_expression(Node* node) { bool TemplateAssertionPredicateExpressionNode::is_template_assertion_predicate(Node* node) { return node->is_If() && node->in(1)->is_Opaque4(); } + +// Is current node pointed to by iterator a predicate? +bool PredicateEntryIterator::has_next() const { + return ParsePredicate::is_predicate(_current) || + RegularPredicateWithUCT::is_predicate(_current) || + AssertionPredicateWithHalt::is_predicate(_current); +} + +// Skip the current predicate pointed to by iterator by returning the input into the predicate. This could possibly be +// a non-predicate node. +Node* PredicateEntryIterator::next_entry() { + assert(has_next(), "current must be predicate"); + _current = _current->in(0)->in(0); + return _current; +} diff --git a/src/hotspot/share/opto/predicates.hpp b/src/hotspot/share/opto/predicates.hpp index bf12aeb6a51..9cac98eb993 100644 --- a/src/hotspot/share/opto/predicates.hpp +++ b/src/hotspot/share/opto/predicates.hpp @@ -26,6 +26,7 @@ #define SHARE_OPTO_PREDICATES_HPP #include "opto/cfgnode.hpp" +#include "opto/connode.hpp" #include "opto/opaquenode.hpp" /* @@ -199,9 +200,6 @@ class AssertionPredicatesWithHalt : public StackObj { Node* _entry; static Node* find_entry(Node* start_proj); - static bool has_assertion_predicate_opaque(const Node* predicate_proj); - static bool has_halt(const Node* success_proj); - static bool is_assertion_predicate_success_proj(const Node* predicate_proj); public: AssertionPredicatesWithHalt(Node* assertion_predicate_proj) : _entry(find_entry(assertion_predicate_proj)) {} @@ -213,13 +211,37 @@ class AssertionPredicatesWithHalt : public StackObj { } }; +// Class to represent a single Assertion Predicate with a HaltNode. This could either be: +// - A Template Assertion Predicate. +// - An Initialized Assertion Predicate. +// Note that all other Regular Predicates have an UCT node. +class AssertionPredicateWithHalt : public StackObj { + static bool has_assertion_predicate_opaque(const Node* predicate_proj); + static bool has_halt(const Node* success_proj); + public: + static bool is_predicate(const Node* maybe_success_proj); +}; + +// Class to represent a single Regular Predicate with an UCT. This could either be: +// - A Runtime Predicate +// - A Template Assertion Predicate +// Note that all other Regular Predicates have a Halt node. +class RegularPredicateWithUCT : public StackObj { + static Deoptimization::DeoptReason uncommon_trap_reason(IfProjNode* if_proj); + static bool may_be_predicate_if(Node* node); + + public: + static bool is_predicate(Node* maybe_success_proj); + static bool is_predicate(Node* node, Deoptimization::DeoptReason deopt_reason); +}; + // Class to represent a Parse Predicate. class ParsePredicate : public StackObj { ParsePredicateSuccessProj* _success_proj; ParsePredicateNode* _parse_predicate_node; Node* _entry; - IfTrueNode* init_success_proj(const Node* parse_predicate_proj) const { + static IfTrueNode* init_success_proj(const Node* parse_predicate_proj) { assert(parse_predicate_proj != nullptr, "must not be null"); return parse_predicate_proj->isa_IfTrue(); } @@ -253,13 +275,12 @@ class ParsePredicate : public StackObj { assert(is_valid(), "must be valid"); return _success_proj; } + + static bool is_predicate(Node* maybe_success_proj); }; // Utility class for queries on Runtime Predicates. class RuntimePredicate : public StackObj { - static Deoptimization::DeoptReason uncommon_trap_reason(IfProjNode* if_proj); - static bool may_be_runtime_predicate_if(Node* node); - public: static bool is_success_proj(Node* node, Deoptimization::DeoptReason deopt_reason); }; @@ -473,4 +494,17 @@ class ParsePredicateIterator : public StackObj { ParsePredicateNode* next(); }; + +// Special predicate iterator that can be used to walk through predicate entries, regardless of whether the predicate +// belongs to the same loop or not (i.e. leftovers from already folded nodes). The iterator returns the next entry +// to a predicate. +class PredicateEntryIterator : public StackObj { + Node* _current; + + public: + explicit PredicateEntryIterator(Node* start) : _current(start) {}; + + bool has_next() const; + Node* next_entry(); +}; #endif // SHARE_OPTO_PREDICATES_HPP diff --git a/src/hotspot/share/opto/superword.cpp b/src/hotspot/share/opto/superword.cpp index d36e306a1e9..f78a9b63926 100644 --- a/src/hotspot/share/opto/superword.cpp +++ b/src/hotspot/share/opto/superword.cpp @@ -481,10 +481,9 @@ bool SuperWord::SLP_extract() { filter_packs_for_profitable(); DEBUG_ONLY(verify_packs();) + DEBUG_ONLY(verify_no_extract()); - schedule(); - - return output(); + return schedule_and_apply(); } // Find the "seed" memops pairs. These are pairs that we strongly suspect would lead to vectorization. @@ -1466,7 +1465,7 @@ const AlignmentSolution* SuperWord::pack_alignment_solution(const Node_List* pac // that the packs impose. Remove packs that do not have a compatible solution. void SuperWord::filter_packs_for_alignment() { // We do not need to filter if no alignment is required. - if (!vectors_should_be_aligned()) { + if (!VLoop::vectors_should_be_aligned()) { return; } @@ -1592,20 +1591,12 @@ bool SuperWord::implemented(const Node_List* pack, const uint size) const { } else if (p0->is_Cmp()) { // Cmp -> Bool -> Cmove retValue = UseVectorCmov; - } else if (requires_long_to_int_conversion(opc)) { - // Java API for Long.bitCount/numberOfLeadingZeros/numberOfTrailingZeros - // returns int type, but Vector API for them returns long type. To unify - // the implementation in backend, superword splits the vector implementation - // for Java API into an execution node with long type plus another node - // converting long to int. + } else if (VectorNode::is_scalar_op_that_returns_int_but_vector_op_returns_long(opc)) { + // Requires extra vector long -> int conversion. retValue = VectorNode::implemented(opc, size, T_LONG) && VectorCastNode::implemented(Op_ConvL2I, size, T_LONG, T_INT); } else { - // Vector unsigned right shift for signed subword types behaves differently - // from Java Spec. But when the shift amount is a constant not greater than - // the number of sign extended bits, the unsigned right shift can be - // vectorized to a signed right shift. - if (VectorNode::can_transform_shift_op(p0, velt_basic_type(p0))) { + if (VectorNode::can_use_RShiftI_instead_of_URShiftI(p0, velt_basic_type(p0))) { opc = Op_RShiftI; } retValue = VectorNode::implemented(opc, size, velt_basic_type(p0)); @@ -1630,36 +1621,87 @@ uint SuperWord::max_implemented_size(const Node_List* pack) { } } -// Java API for Long.bitCount/numberOfLeadingZeros/numberOfTrailingZeros -// returns int type, but Vector API for them returns long type. To unify -// the implementation in backend, superword splits the vector implementation -// for Java API into an execution node with long type plus another node -// converting long to int. -bool SuperWord::requires_long_to_int_conversion(int opc) { - switch(opc) { - case Op_PopCountL: - case Op_CountLeadingZerosL: - case Op_CountTrailingZerosL: - return true; - default: - return false; +// If the j-th input for all nodes in the pack is the same input: return it, else nullptr. +Node* PackSet::same_inputs_at_index_or_null(const Node_List* pack, const int index) const { + Node* p0_in = pack->at(0)->in(index); + for (uint i = 1; i < pack->size(); i++) { + if (pack->at(i)->in(index) != p0_in) { + return nullptr; // not same + } } + return p0_in; } -//------------------------------same_inputs-------------------------- -// For pack p, are all idx operands the same? -bool SuperWord::same_inputs(const Node_List* p, int idx) const { - Node* p0 = p->at(0); - uint vlen = p->size(); - Node* p0_def = p0->in(idx); - for (uint i = 1; i < vlen; i++) { - Node* pi = p->at(i); - Node* pi_def = pi->in(idx); - if (p0_def != pi_def) { - return false; +VTransformBoolTest PackSet::get_bool_test(const Node_List* bool_pack) const { + BoolNode* bol = bool_pack->at(0)->as_Bool(); + BoolTest::mask mask = bol->_test._test; + bool is_negated = false; + assert(mask == BoolTest::eq || + mask == BoolTest::ne || + mask == BoolTest::ge || + mask == BoolTest::gt || + mask == BoolTest::lt || + mask == BoolTest::le, + "Bool should be one of: eq, ne, ge, gt, lt, le"); + +#ifdef ASSERT + for (uint j = 0; j < bool_pack->size(); j++) { + Node* m = bool_pack->at(j); + assert(m->as_Bool()->_test._test == mask, + "all bool nodes must have same test"); + } +#endif + + CmpNode* cmp0 = bol->in(1)->as_Cmp(); + assert(get_pack(cmp0) != nullptr, "Bool must have matching Cmp pack"); + + if (cmp0->Opcode() == Op_CmpF || cmp0->Opcode() == Op_CmpD) { + // If we have a Float or Double comparison, we must be careful with + // handling NaN's correctly. CmpF and CmpD have a return code, as + // they are based on the java bytecodes fcmpl/dcmpl: + // -1: cmp_in1 < cmp_in2, or at least one of the two is a NaN + // 0: cmp_in1 == cmp_in2 (no NaN) + // 1: cmp_in1 > cmp_in2 (no NaN) + // + // The "mask" selects which of the [-1, 0, 1] cases lead to "true". + // + // Note: ordered (O) comparison returns "false" if either input is NaN. + // unordered (U) comparison returns "true" if either input is NaN. + // + // The VectorMaskCmpNode does a comparison directly on in1 and in2, in the java + // standard way (all comparisons are ordered, except NEQ is unordered). + // + // In the following, "mask" already matches the cmp code for VectorMaskCmpNode: + // BoolTest::eq: Case 0 -> EQ_O + // BoolTest::ne: Case -1, 1 -> NEQ_U + // BoolTest::ge: Case 0, 1 -> GE_O + // BoolTest::gt: Case 1 -> GT_O + // + // But the lt and le comparisons must be converted from unordered to ordered: + // BoolTest::lt: Case -1 -> LT_U -> VectorMaskCmp would interpret lt as LT_O + // BoolTest::le: Case -1, 0 -> LE_U -> VectorMaskCmp would interpret le as LE_O + // + if (mask == BoolTest::lt || mask == BoolTest::le) { + // Negating the mask gives us the negated result, since all non-NaN cases are + // negated, and the unordered (U) comparisons are turned into ordered (O) comparisons. + // VectorMaskCmp(LT_U, in1_cmp, in2_cmp) + // <==> NOT VectorMaskCmp(GE_O, in1_cmp, in2_cmp) + // VectorMaskCmp(LE_U, in1_cmp, in2_cmp) + // <==> NOT VectorMaskCmp(GT_O, in1_cmp, in2_cmp) + // + // When a VectorBlend uses the negated mask, it can simply swap its blend-inputs: + // VectorBlend( VectorMaskCmp(LT_U, in1_cmp, in2_cmp), in1_blend, in2_blend) + // <==> VectorBlend(NOT VectorMaskCmp(GE_O, in1_cmp, in2_cmp), in1_blend, in2_blend) + // <==> VectorBlend( VectorMaskCmp(GE_O, in1_cmp, in2_cmp), in2_blend, in1_blend) + // VectorBlend( VectorMaskCmp(LE_U, in1_cmp, in2_cmp), in1_blend, in2_blend) + // <==> VectorBlend(NOT VectorMaskCmp(GT_O, in1_cmp, in2_cmp), in1_blend, in2_blend) + // <==> VectorBlend( VectorMaskCmp(GT_O, in1_cmp, in2_cmp), in2_blend, in1_blend) + mask = bol->_test.negate(); + is_negated = true; } } - return true; + + return VTransformBoolTest(mask, is_negated); } //------------------------------profitable--------------------------- @@ -1696,10 +1738,9 @@ bool SuperWord::profitable(const Node_List* p) const { // case (different shift counts) because it is not supported yet. Node* cnt = p0->in(2); Node_List* cnt_pk = get_pack(cnt); - if (cnt_pk != nullptr) - return false; - if (!same_inputs(p, 2)) + if (cnt_pk != nullptr || _packset.same_inputs_at_index_or_null(p, 2) == nullptr) { return false; + } } if (!p0->is_Store()) { // For now, return false if not all uses are vector. @@ -2042,7 +2083,9 @@ class PacksetGraph { } }; -// The C2 graph (specifically the memory graph), needs to be re-ordered. +// We want to replace the packed scalars from the PackSet and replace them +// with vector operations. This requires scheduling and re-ordering the memory +// graph. We take these steps: // (1) Build the PacksetGraph. It combines the dependency graph with the // packset. The PacksetGraph gives us the dependencies that must be // respected after scheduling. @@ -2050,10 +2093,11 @@ class PacksetGraph { // a linear order of all memops in the body. The order respects the // dependencies of the PacksetGraph. // (3) If the PacksetGraph has cycles, we cannot schedule. Abort. -// (4) Use the memops_schedule to re-order the memops in all slices. -void SuperWord::schedule() { - if (_packset.length() == 0) { - return; // empty packset +// (4) Apply the vectorization, including re-ordering the memops and replacing +// packed scalars with vector operations. +bool SuperWord::schedule_and_apply() { + if (_packset.is_empty()) { + return false; } ResourceMark rm; @@ -2079,27 +2123,40 @@ void SuperWord::schedule() { } #endif _packset.clear(); - return; + return false; } -#ifndef PRODUCT - if (is_trace_superword_info()) { - tty->print_cr("SuperWord::schedule: memops_schedule:"); - memops_schedule.dump(); - } -#endif + // (4) Apply the vectorization, including re-ordering the memops. + return apply(memops_schedule); +} +bool SuperWord::apply(Node_List& memops_schedule) { + Compile* C = phase()->C; CountedLoopNode* cl = lpt()->_head->as_CountedLoop(); - phase()->C->print_method(PHASE_SUPERWORD1_BEFORE_SCHEDULE, 4, cl); + C->print_method(PHASE_AUTO_VECTORIZATION1_BEFORE_APPLY, 4, cl); - // (4) Use the memops_schedule to re-order the memops in all slices. - schedule_reorder_memops(memops_schedule); -} + apply_memops_reordering_with_schedule(memops_schedule); + C->print_method(PHASE_AUTO_VECTORIZATION2_AFTER_REORDER, 4, cl); + adjust_pre_loop_limit_to_align_main_loop_vectors(); + C->print_method(PHASE_AUTO_VECTORIZATION3_AFTER_ADJUST_LIMIT, 4, cl); + + bool is_success = apply_vectorization(); + C->print_method(PHASE_AUTO_VECTORIZATION4_AFTER_APPLY, 4, cl); + + return is_success; +} // Reorder the memory graph for all slices in parallel. We walk over the schedule once, // and track the current memory state of each slice. -void SuperWord::schedule_reorder_memops(Node_List &memops_schedule) { +void SuperWord::apply_memops_reordering_with_schedule(Node_List& memops_schedule) { +#ifndef PRODUCT + if (is_trace_superword_info()) { + tty->print_cr("\nSuperWord::apply_memops_reordering_with_schedule:"); + memops_schedule.dump(); + } +#endif + int max_slices = phase()->C->num_alias_types(); // When iterating over the memops_schedule, we keep track of the current memory state, // which is the Phi or a store in the loop. @@ -2180,32 +2237,24 @@ void SuperWord::schedule_reorder_memops(Node_List &memops_schedule) { } } -//------------------------------output--------------------------- // Convert packs into vector node operations // At this point, all correctness and profitability checks have passed. // We start the irreversible process of editing the C2 graph. Should // there be an unexpected situation (assert fails), then we can only // bail out of the compilation, as the graph has already been partially // modified. We bail out, and retry without SuperWord. -bool SuperWord::output() { +bool SuperWord::apply_vectorization() { CountedLoopNode *cl = lpt()->_head->as_CountedLoop(); assert(cl->is_main_loop(), "SLP should only work on main loops"); Compile* C = phase()->C; - if (_packset.is_empty()) { - return false; - } + assert(!_packset.is_empty(), "vectorization requires non-empty packset"); #ifndef PRODUCT if (TraceLoopOpts) { - tty->print("SuperWord::output "); + tty->print("SuperWord::apply_vectorization "); lpt()->dump_head(); } #endif - phase()->C->print_method(PHASE_SUPERWORD2_BEFORE_OUTPUT, 4, cl); - - adjust_pre_loop_limit_to_align_main_loop_vectors(); - - DEBUG_ONLY(verify_no_extract()); uint max_vlen_in_bytes = 0; uint max_vlen = 0; @@ -2214,7 +2263,7 @@ bool SuperWord::output() { Node* n = body().at(i); Node_List* p = get_pack(n); if (p != nullptr && n == p->at(p->size()-1)) { - // After schedule_reorder_memops, we know that the memops have the same order in the pack + // After apply_memops_reordering_with_schedule, we know that the memops have the same order in the pack // as in the memory slice. Hence, "first" is the first memop in the slice from the pack, // and "n" is the last node in the slice from the pack. Node* first = p->at(0); @@ -2294,79 +2343,32 @@ bool SuperWord::output() { BoolNode* bol = n->in(1)->as_Bool(); assert(bol != nullptr, "must have Bool above CMove"); - BoolTest::mask bol_test = bol->_test._test; - assert(bol_test == BoolTest::eq || - bol_test == BoolTest::ne || - bol_test == BoolTest::ge || - bol_test == BoolTest::gt || - bol_test == BoolTest::lt || - bol_test == BoolTest::le, - "CMove bool should be one of: eq,ne,ge,ge,lt,le"); - Node_List* p_bol = get_pack(bol); - assert(p_bol != nullptr, "CMove must have matching Bool pack"); - -#ifdef ASSERT - for (uint j = 0; j < p_bol->size(); j++) { - Node* m = p_bol->at(j); - assert(m->as_Bool()->_test._test == bol_test, - "all bool nodes must have same test"); - } -#endif + Node_List* bool_pack = get_pack(bol); + assert(bool_pack != nullptr, "CMove must have matching Bool pack"); CmpNode* cmp = bol->in(1)->as_Cmp(); assert(cmp != nullptr, "must have cmp above CMove"); - Node_List* p_cmp = get_pack(cmp); - assert(p_cmp != nullptr, "Bool must have matching Cmp pack"); + Node_List* cmp_pack = get_pack(cmp); + assert(cmp_pack != nullptr, "Bool must have matching Cmp pack"); - Node* cmp_in1 = vector_opd(p_cmp, 1); - Node* cmp_in2 = vector_opd(p_cmp, 2); + Node* cmp_in1 = vector_opd(cmp_pack, 1); + Node* cmp_in2 = vector_opd(cmp_pack, 2); Node* blend_in1 = vector_opd(p, 2); Node* blend_in2 = vector_opd(p, 3); - if (cmp->Opcode() == Op_CmpF || cmp->Opcode() == Op_CmpD) { - // If we have a Float or Double comparison, we must be careful with - // handling NaN's correctly. CmpF and CmpD have a return code, as - // they are based on the java bytecodes fcmpl/dcmpl: - // -1: cmp_in1 < cmp_in2, or at least one of the two is a NaN - // 0: cmp_in1 == cmp_in2 (no NaN) - // 1: cmp_in1 > cmp_in2 (no NaN) - // - // The "bol_test" selects which of the [-1, 0, 1] cases lead to "true". - // - // Note: ordered (O) comparison returns "false" if either input is NaN. - // unordered (U) comparison returns "true" if either input is NaN. - // - // The VectorMaskCmpNode does a comparison directly on in1 and in2, in the java - // standard way (all comparisons are ordered, except NEQ is unordered). - // - // In the following, "bol_test" already matches the cmp code for VectorMaskCmpNode: - // BoolTest::eq: Case 0 -> EQ_O - // BoolTest::ne: Case -1, 1 -> NEQ_U - // BoolTest::ge: Case 0, 1 -> GE_O - // BoolTest::gt: Case 1 -> GT_O - // - // But the lt and le comparisons must be converted from unordered to ordered: - // BoolTest::lt: Case -1 -> LT_U -> VectorMaskCmp would interpret lt as LT_O - // BoolTest::le: Case -1, 0 -> LE_U -> VectorMaskCmp would interpret le as LE_O - // - if (bol_test == BoolTest::lt || bol_test == BoolTest::le) { - // Negating the bol_test and swapping the blend-inputs leaves all non-NaN cases equal, - // but converts the unordered (U) to an ordered (O) comparison. - // VectorBlend(VectorMaskCmp(LT_U, in1_cmp, in2_cmp), in1_blend, in2_blend) - // <==> VectorBlend(VectorMaskCmp(GE_O, in1_cmp, in2_cmp), in2_blend, in1_blend) - // VectorBlend(VectorMaskCmp(LE_U, in1_cmp, in2_cmp), in1_blend, in2_blend) - // <==> VectorBlend(VectorMaskCmp(GT_O, in1_cmp, in2_cmp), in2_blend, in1_blend) - bol_test = bol->_test.negate(); - swap(blend_in1, blend_in2); - } + VTransformBoolTest bool_test = _packset.get_bool_test(bool_pack); + BoolTest::mask test_mask = bool_test._mask; + if (bool_test._is_negated) { + // We can cancel out the negation by swapping the blend inputs. + swap(blend_in1, blend_in2); } // VectorMaskCmp - ConINode* bol_test_node = igvn().intcon((int)bol_test); + ConINode* test_mask_node = igvn().intcon((int)test_mask); BasicType bt = velt_basic_type(cmp); const TypeVect* vt = TypeVect::make(bt, vlen); - VectorNode* mask = new VectorMaskCmpNode(bol_test, cmp_in1, cmp_in2, bol_test_node, vt); + VectorNode* mask = new VectorMaskCmpNode(test_mask, cmp_in1, cmp_in2, test_mask_node, vt); phase()->register_new_node_with_ctrl_of(mask, p->at(0)); igvn()._worklist.push(mask); @@ -2408,40 +2410,23 @@ bool SuperWord::output() { vlen_in_bytes = in2->as_Vector()->length_in_bytes(); } } else { - // Vector unsigned right shift for signed subword types behaves differently - // from Java Spec. But when the shift amount is a constant not greater than - // the number of sign extended bits, the unsigned right shift can be - // vectorized to a signed right shift. - if (VectorNode::can_transform_shift_op(n, velt_basic_type(n))) { + if (VectorNode::can_use_RShiftI_instead_of_URShiftI(n, velt_basic_type(n))) { opc = Op_RShiftI; } vn = VectorNode::make(opc, in1, in2, vlen, velt_basic_type(n)); vlen_in_bytes = vn->as_Vector()->length_in_bytes(); } - } else if (opc == Op_SqrtF || opc == Op_SqrtD || - opc == Op_AbsF || opc == Op_AbsD || - opc == Op_AbsI || opc == Op_AbsL || - opc == Op_NegF || opc == Op_NegD || - opc == Op_RoundF || opc == Op_RoundD || - opc == Op_ReverseBytesI || opc == Op_ReverseBytesL || - opc == Op_ReverseBytesUS || opc == Op_ReverseBytesS || - opc == Op_ReverseI || opc == Op_ReverseL || - opc == Op_PopCountI || opc == Op_CountLeadingZerosI || - opc == Op_CountTrailingZerosI) { + } else if (VectorNode::is_scalar_unary_op_with_equal_input_and_output_types(opc)) { assert(n->req() == 2, "only one input expected"); Node* in = vector_opd(p, 1); vn = VectorNode::make(opc, in, nullptr, vlen, velt_basic_type(n)); vlen_in_bytes = vn->as_Vector()->length_in_bytes(); - } else if (requires_long_to_int_conversion(opc)) { - // Java API for Long.bitCount/numberOfLeadingZeros/numberOfTrailingZeros - // returns int type, but Vector API for them returns long type. To unify - // the implementation in backend, superword splits the vector implementation - // for Java API into an execution node with long type plus another node - // converting long to int. + } else if (VectorNode::is_scalar_op_that_returns_int_but_vector_op_returns_long(opc)) { assert(n->req() == 2, "only one input expected"); Node* in = vector_opd(p, 1); Node* longval = VectorNode::make(opc, in, nullptr, vlen, T_LONG); phase()->register_new_node_with_ctrl_of(longval, first); + // Requires extra vector long -> int conversion. vn = VectorCastNode::make(Op_VectorCastL2X, longval, T_INT, vlen); vlen_in_bytes = vn->as_Vector()->length_in_bytes(); } else if (VectorNode::is_convert_opcode(opc)) { @@ -2525,8 +2510,6 @@ bool SuperWord::output() { } } - phase()->C->print_method(PHASE_SUPERWORD3_AFTER_OUTPUT, 4, cl); - return true; } @@ -2537,13 +2520,13 @@ Node* SuperWord::vector_opd(Node_List* p, int opd_idx) { uint vlen = p->size(); Node* opd = p0->in(opd_idx); CountedLoopNode *cl = lpt()->_head->as_CountedLoop(); - bool have_same_inputs = same_inputs(p, opd_idx); + Node* same_input = _packset.same_inputs_at_index_or_null(p, opd_idx); // Insert index population operation to create a vector of increasing // indices starting from the iv value. In some special unrolled loops // (see JDK-8286125), we need scalar replications of the iv value if // all inputs are the same iv, so we do a same inputs check here. - if (opd == iv() && !have_same_inputs) { + if (opd == iv() && same_input == nullptr) { BasicType p0_bt = velt_basic_type(p0); BasicType iv_bt = is_subword_type(p0_bt) ? p0_bt : T_INT; assert(VectorNode::is_populate_index_supported(iv_bt), "Should support"); @@ -2554,7 +2537,7 @@ Node* SuperWord::vector_opd(Node_List* p, int opd_idx) { return vn; } - if (have_same_inputs) { + if (same_input != nullptr) { if (opd->is_Vector() || opd->is_LoadVector()) { if (opd_idx == 2 && VectorNode::is_shift(p0)) { assert(false, "shift's count can't be vector"); @@ -2772,11 +2755,11 @@ bool SuperWord::is_vector_use(Node* use, int u_idx) const { // Reduction: first input is internal connection. if (is_marked_reduction(use) && u_idx == 1) { -#ifdef ASSERT - for (uint i = 1; i < u_pk->size(); i++) { - assert(u_pk->at(i - 1) == u_pk->at(i)->in(1), "internal connection"); + for (uint i = 1; i < u_pk->size(); i++) { + if (u_pk->at(i - 1) != u_pk->at(i)->in(1)) { + return false; // not internally connected } -#endif + } return true; } @@ -2811,11 +2794,7 @@ bool SuperWord::is_vector_use(Node* use, int u_idx) const { } if (VectorNode::is_muladds2i(use)) { - // MulAddS2I takes shorts and produces ints. - if (u_pk->size() * 2 != d_pk->size()) { - return false; - } - return true; + return _packset.is_muladds2i_pack_with_pack_inputs(u_pk); } if (u_pk->size() != d_pk->size()) { @@ -2832,6 +2811,59 @@ bool SuperWord::is_vector_use(Node* use, int u_idx) const { return true; } +// MulAddS2I takes 4 shorts and produces an int. We can reinterpret +// the 4 shorts as two ints: a = (a0, a1) and b = (b0, b1). +// +// Inputs: 1 2 3 4 +// Offsets: 0 0 1 1 +// v = MulAddS2I(a, b) = a0 * b0 + a1 * b1 +// +// But permutations are possible, because add and mul are commutative. For +// simplicity, the first input is always either a0 or a1. These are all +// the possible permutations: +// +// v = MulAddS2I(a, b) = a0 * b0 + a1 * b1 (case 1) +// v = MulAddS2I(a, b) = a0 * b0 + b1 * a1 (case 2) +// v = MulAddS2I(a, b) = a1 * b1 + a0 * b0 (case 3) +// v = MulAddS2I(a, b) = a1 * b1 + b0 * a0 (case 4) +// +// To vectorize, we expect (a0, a1) to be consecutive in one input pack, +// and (b0, b1) in the other input pack. Thus, both a and b are strided, +// with stride = 2. Further, a0 and b0 have offset 0, whereas a1 and b1 +// have offset 1. +bool PackSet::is_muladds2i_pack_with_pack_inputs(const Node_List* pack) const { + assert(VectorNode::is_muladds2i(pack->at(0)), "must be MulAddS2I"); + + bool pack1_has_offset_0 = (strided_pack_input_at_index_or_null(pack, 1, 2, 0) != nullptr); + Node_List* pack1 = strided_pack_input_at_index_or_null(pack, 1, 2, pack1_has_offset_0 ? 0 : 1); + Node_List* pack2 = strided_pack_input_at_index_or_null(pack, 2, 2, pack1_has_offset_0 ? 0 : 1); + Node_List* pack3 = strided_pack_input_at_index_or_null(pack, 3, 2, pack1_has_offset_0 ? 1 : 0); + Node_List* pack4 = strided_pack_input_at_index_or_null(pack, 4, 2, pack1_has_offset_0 ? 1 : 0); + + return pack1 != nullptr && + pack2 != nullptr && + pack3 != nullptr && + pack4 != nullptr && + ((pack1 == pack3 && pack2 == pack4) || // case 1 or 3 + (pack1 == pack4 && pack2 == pack3)); // case 2 or 4 +} + +Node_List* PackSet::strided_pack_input_at_index_or_null(const Node_List* pack, const int index, const int stride, const int offset) const { + Node* def0 = pack->at(0)->in(index); + + Node_List* pack_in = get_pack(def0); + if (pack_in == nullptr || pack->size() * stride != pack_in->size()) { + return nullptr; // size mismatch + } + + for (uint i = 1; i < pack->size(); i++) { + if (pack->at(i)->in(index) != pack_in->at(i * stride + offset)) { + return nullptr; // use-def mismatch + } + } + return pack_in; +} + // Check if the output type of def is compatible with the input type of use, i.e. if the // types have the same size. bool SuperWord::is_velt_basic_type_compatible_use_def(Node* use, Node* def) const { @@ -2849,7 +2881,7 @@ bool SuperWord::is_velt_basic_type_compatible_use_def(Node* use, Node* def) cons assert(is_java_primitive(def_bt), "sanity %s", type2name(def_bt)); // Nodes like Long.bitCount: expect long input, and int output. - if (requires_long_to_int_conversion(use->Opcode())) { + if (VectorNode::is_scalar_op_that_returns_int_but_vector_op_returns_long(use->Opcode())) { return type2aelembytes(def_bt) == 8 && type2aelembytes(use_bt) == 4; } @@ -2996,7 +3028,7 @@ VStatus VLoopBody::construct() { BasicType SuperWord::longer_type_for_conversion(Node* n) const { if (!(VectorNode::is_convert_opcode(n->Opcode()) || - requires_long_to_int_conversion(n->Opcode())) || + VectorNode::is_scalar_op_that_returns_int_but_vector_op_returns_long(n->Opcode())) || !in_bb(n->in(1))) { return T_ILLEGAL; } @@ -3173,7 +3205,7 @@ LoadNode::ControlDependency SuperWord::control_dependency(Node_List* p) { // determined by SuperWord::filter_packs_for_alignment(). void SuperWord::determine_mem_ref_and_aw_for_main_loop_alignment() { if (_mem_ref_for_main_loop_alignment != nullptr) { - assert(vectors_should_be_aligned(), "mem_ref only set if filtered for alignment"); + assert(VLoop::vectors_should_be_aligned(), "mem_ref only set if filtered for alignment"); return; } diff --git a/src/hotspot/share/opto/superword.hpp b/src/hotspot/share/opto/superword.hpp index a07cfcd5b18..fb91d014fae 100644 --- a/src/hotspot/share/opto/superword.hpp +++ b/src/hotspot/share/opto/superword.hpp @@ -362,6 +362,11 @@ class PackSet : public StackObj { } } + Node_List* strided_pack_input_at_index_or_null(const Node_List* pack, const int index, const int stride, const int offset) const; + bool is_muladds2i_pack_with_pack_inputs(const Node_List* pack) const; + Node* same_inputs_at_index_or_null(const Node_List* pack, const int index) const; + VTransformBoolTest get_bool_test(const Node_List* bool_pack) const; + private: SplitStatus split_pack(const char* split_name, Node_List* pack, SplitTask task); public: @@ -545,12 +550,6 @@ class SuperWord : public ResourceObj { // Accessors Arena* arena() { return &_arena; } - // should we align vector memory references on this platform? - bool vectors_should_be_aligned() { return !Matcher::misaligned_vectors_ok() || AlignVector; } - - // For pack p, are all idx operands the same? - bool same_inputs(const Node_List* p, int idx) const; - // CloneMap utilities bool same_origin_idx(Node* a, Node* b) const; bool same_generation(Node* a, Node* b) const; @@ -600,13 +599,10 @@ class SuperWord : public ResourceObj { DEBUG_ONLY(void verify_packs() const;) - // Adjust the memory graph for the packed operations - void schedule(); - // Helper function for schedule, that reorders all memops, slice by slice, according to the schedule - void schedule_reorder_memops(Node_List &memops_schedule); - - // Convert packs into vector node operations - bool output(); + bool schedule_and_apply(); + bool apply(Node_List& memops_schedule); + void apply_memops_reordering_with_schedule(Node_List& memops_schedule); + bool apply_vectorization(); // Create a vector operand for the nodes in pack p for operand: in(opd_idx) Node* vector_opd(Node_List* p, int opd_idx); @@ -632,8 +628,6 @@ class SuperWord : public ResourceObj { // Return the longer type for vectorizable type-conversion node or illegal type for other nodes. BasicType longer_type_for_conversion(Node* n) const; - static bool requires_long_to_int_conversion(int opc); - bool is_velt_basic_type_compatible_use_def(Node* use, Node* def) const; static LoadNode::ControlDependency control_dependency(Node_List* p); diff --git a/src/hotspot/share/opto/vectorization.cpp b/src/hotspot/share/opto/vectorization.cpp index ac575d7192c..01b235e8b27 100644 --- a/src/hotspot/share/opto/vectorization.cpp +++ b/src/hotspot/share/opto/vectorization.cpp @@ -807,24 +807,26 @@ void VPointer::maybe_add_to_invar(Node* new_invar, bool negate) { _invar = register_if_new(add); } +// We use two comparisons, because a subtraction could underflow. +#define RETURN_CMP_VALUE_IF_NOT_EQUAL(a, b) \ + if (a < b) { return -1; } \ + if (a > b) { return 1; } + // To be in the same group, two VPointers must be the same, // except for the offset. int VPointer::cmp_for_sort_by_group(const VPointer** p1, const VPointer** p2) { const VPointer* a = *p1; const VPointer* b = *p2; - int cmp_base = a->base()->_idx - b->base()->_idx; - if (cmp_base != 0) { return cmp_base; } - - int cmp_opcode = a->mem()->Opcode() - b->mem()->Opcode(); - if (cmp_opcode != 0) { return cmp_opcode; } + RETURN_CMP_VALUE_IF_NOT_EQUAL(a->base()->_idx, b->base()->_idx); + RETURN_CMP_VALUE_IF_NOT_EQUAL(a->mem()->Opcode(), b->mem()->Opcode()); + RETURN_CMP_VALUE_IF_NOT_EQUAL(a->scale_in_bytes(), b->scale_in_bytes()); - int cmp_scale = a->scale_in_bytes() - b->scale_in_bytes(); - if (cmp_scale != 0) { return cmp_scale; } + int a_inva_idx = a->invar() == nullptr ? 0 : a->invar()->_idx; + int b_inva_idx = b->invar() == nullptr ? 0 : b->invar()->_idx; + RETURN_CMP_VALUE_IF_NOT_EQUAL(a_inva_idx, b_inva_idx); - int cmp_invar = (a->invar() == nullptr ? 0 : a->invar()->_idx) - - (b->invar() == nullptr ? 0 : b->invar()->_idx); - return cmp_invar; + return 0; // equal } // We compare by group, then by offset, and finally by node idx. @@ -835,10 +837,9 @@ int VPointer::cmp_for_sort(const VPointer** p1, const VPointer** p2) { const VPointer* a = *p1; const VPointer* b = *p2; - int cmp_offset = a->offset_in_bytes() - b->offset_in_bytes(); - if (cmp_offset != 0) { return cmp_offset; } - - return a->mem()->_idx - b->mem()->_idx; + RETURN_CMP_VALUE_IF_NOT_EQUAL(a->offset_in_bytes(), b->offset_in_bytes()); + RETURN_CMP_VALUE_IF_NOT_EQUAL(a->mem()->_idx, b->mem()->_idx); + return 0; // equal } #ifndef PRODUCT diff --git a/src/hotspot/share/opto/vectorization.hpp b/src/hotspot/share/opto/vectorization.hpp index 0acc78ed1a1..c9f54594910 100644 --- a/src/hotspot/share/opto/vectorization.hpp +++ b/src/hotspot/share/opto/vectorization.hpp @@ -129,6 +129,9 @@ class VLoop : public StackObj { int estimated_body_length() const { return lpt()->_body.size(); }; int estimated_node_count() const { return (int)(1.10 * phase()->C->unique()); }; + // Should we align vector memory references on this platform? + static bool vectors_should_be_aligned() { return !Matcher::misaligned_vectors_ok() || AlignVector; } + #ifndef PRODUCT const VTrace& vtrace() const { return _vtrace; } @@ -1320,4 +1323,12 @@ class AlignmentSolver { #endif }; +struct VTransformBoolTest { + const BoolTest::mask _mask; + const bool _is_negated; + + VTransformBoolTest(const BoolTest::mask mask, bool is_negated) : + _mask(mask), _is_negated(is_negated) {} +}; + #endif // SHARE_OPTO_VECTORIZATION_HPP diff --git a/src/hotspot/share/opto/vectornode.cpp b/src/hotspot/share/opto/vectornode.cpp index d560f112039..72b49c043b6 100644 --- a/src/hotspot/share/opto/vectornode.cpp +++ b/src/hotspot/share/opto/vectornode.cpp @@ -507,7 +507,11 @@ bool VectorNode::is_shift_opcode(int opc) { } } -bool VectorNode::can_transform_shift_op(Node* n, BasicType bt) { +// Vector unsigned right shift for signed subword types behaves differently +// from Java Spec. But when the shift amount is a constant not greater than +// the number of sign extended bits, the unsigned right shift can be +// vectorized to a signed right shift. +bool VectorNode::can_use_RShiftI_instead_of_URShiftI(Node* n, BasicType bt) { if (n->Opcode() != Op_URShiftI) { return false; } @@ -920,6 +924,50 @@ bool VectorNode::is_vector_bitwise_not_pattern(Node* n) { return false; } +bool VectorNode::is_scalar_unary_op_with_equal_input_and_output_types(int opc) { + switch (opc) { + case Op_SqrtF: + case Op_SqrtD: + case Op_AbsF: + case Op_AbsD: + case Op_AbsI: + case Op_AbsL: + case Op_NegF: + case Op_NegD: + case Op_RoundF: + case Op_RoundD: + case Op_ReverseBytesI: + case Op_ReverseBytesL: + case Op_ReverseBytesUS: + case Op_ReverseBytesS: + case Op_ReverseI: + case Op_ReverseL: + case Op_PopCountI: + case Op_CountLeadingZerosI: + case Op_CountTrailingZerosI: + return true; + default: + return false; + } +} + +// Java API for Long.bitCount/numberOfLeadingZeros/numberOfTrailingZeros +// returns int type, but Vector API for them returns long type. To unify +// the implementation in backend, AutoVectorization splits the vector +// implementation for Java API into an execution node with long type plus +// another node converting long to int. +bool VectorNode::is_scalar_op_that_returns_int_but_vector_op_returns_long(int opc) { + switch (opc) { + case Op_PopCountL: + case Op_CountLeadingZerosL: + case Op_CountTrailingZerosL: + return true; + default: + return false; + } +} + + Node* VectorNode::try_to_gen_masked_vector(PhaseGVN* gvn, Node* node, const TypeVect* vt) { int vopc = node->Opcode(); uint vlen = vt->length(); diff --git a/src/hotspot/share/opto/vectornode.hpp b/src/hotspot/share/opto/vectornode.hpp index 6c5402eb511..23ddebaf338 100644 --- a/src/hotspot/share/opto/vectornode.hpp +++ b/src/hotspot/share/opto/vectornode.hpp @@ -84,7 +84,7 @@ class VectorNode : public TypeNode { static VectorNode* make_mask_node(int vopc, Node* n1, Node* n2, uint vlen, BasicType bt); static bool is_shift_opcode(int opc); - static bool can_transform_shift_op(Node* n, BasicType bt); + static bool can_use_RShiftI_instead_of_URShiftI(Node* n, BasicType bt); static bool is_convert_opcode(int opc); static bool is_minmax_opcode(int opc); @@ -130,6 +130,9 @@ class VectorNode : public TypeNode { return is_vector_shift_count(n->Opcode()); } + static bool is_scalar_unary_op_with_equal_input_and_output_types(int opc); + static bool is_scalar_op_that_returns_int_but_vector_op_returns_long(int opc); + static void trace_new_vector(Node* n, const char* context) { #ifdef ASSERT if (TraceNewVectors) { diff --git a/src/hotspot/share/prims/jvm.cpp b/src/hotspot/share/prims/jvm.cpp index 28e6554d809..c9a962baeee 100644 --- a/src/hotspot/share/prims/jvm.cpp +++ b/src/hotspot/share/prims/jvm.cpp @@ -3734,7 +3734,7 @@ JVM_ENTRY(void, JVM_LogLambdaFormInvoker(JNIEnv *env, jstring line)) } if (ClassListWriter::is_enabled()) { ClassListWriter w; - w.stream()->print_cr("%s %s", LAMBDA_FORM_TAG, c_line); + w.stream()->print_cr("%s %s", ClassListParser::lambda_form_tag(), c_line); } } #endif // INCLUDE_CDS diff --git a/src/hotspot/share/prims/unsafe.cpp b/src/hotspot/share/prims/unsafe.cpp index 880f1f6ab5d..942d9100c29 100644 --- a/src/hotspot/share/prims/unsafe.cpp +++ b/src/hotspot/share/prims/unsafe.cpp @@ -55,6 +55,7 @@ #include "runtime/threadSMR.hpp" #include "runtime/vmOperations.hpp" #include "runtime/vm_version.hpp" +#include "sanitizers/ub.hpp" #include "services/threadService.hpp" #include "utilities/align.hpp" #include "utilities/copy.hpp" @@ -242,6 +243,9 @@ class MemoryAccess : StackObj { return normalize_for_read(*addr()); } + // we use this method at some places for writing to 0 e.g. to cause a crash; + // ubsan does not know that this is the desired behavior + ATTRIBUTE_NO_UBSAN void put(T x) { GuardUnsafeAccess guard(_thread); *addr() = normalize_for_write(x); diff --git a/src/hotspot/share/prims/whitebox.cpp b/src/hotspot/share/prims/whitebox.cpp index a45be186aa4..5ed593b0d2f 100644 --- a/src/hotspot/share/prims/whitebox.cpp +++ b/src/hotspot/share/prims/whitebox.cpp @@ -29,6 +29,7 @@ #include "cds/filemap.hpp" #include "cds/heapShared.hpp" #include "cds/metaspaceShared.hpp" +#include "classfile/classLoader.hpp" #include "classfile/classLoaderDataGraph.hpp" #include "classfile/classLoaderStats.hpp" #include "classfile/classPrinter.hpp" @@ -2115,7 +2116,10 @@ WB_END WB_ENTRY(jboolean, WB_IsCDSIncluded(JNIEnv* env)) #if INCLUDE_CDS - return true; + // An exploded build inhibits use of CDS. Therefore, for the + // purpose of testing, the JVM can be treated as not having CDS + // built in at all. + return ClassLoader::has_jrt_entry(); #else return false; #endif // INCLUDE_CDS @@ -2653,6 +2657,11 @@ WB_ENTRY(void, WB_CleanMetaspaces(JNIEnv* env, jobject target)) ClassLoaderDataGraph::safepoint_and_clean_metaspaces(); WB_END +// Reports resident set size (RSS) in bytes +WB_ENTRY(jlong, WB_Rss(JNIEnv* env, jobject o)) + return os::rss(); +WB_END + #define CC (char*) static JNINativeMethod methods[] = { @@ -2940,6 +2949,7 @@ static JNINativeMethod methods[] = { {CC"setVirtualThreadsNotifyJvmtiMode", CC"(Z)Z", (void*)&WB_SetVirtualThreadsNotifyJvmtiMode}, {CC"preTouchMemory", CC"(JJ)V", (void*)&WB_PreTouchMemory}, {CC"cleanMetaspaces", CC"()V", (void*)&WB_CleanMetaspaces}, + {CC"rss", CC"()J", (void*)&WB_Rss}, }; diff --git a/src/hotspot/share/runtime/arguments.cpp b/src/hotspot/share/runtime/arguments.cpp index 42cb574a784..8c4cac48447 100644 --- a/src/hotspot/share/runtime/arguments.cpp +++ b/src/hotspot/share/runtime/arguments.cpp @@ -34,6 +34,7 @@ #include "compiler/compilerDefinitions.hpp" #include "gc/shared/gcArguments.hpp" #include "gc/shared/gcConfig.hpp" +#include "gc/shared/genArguments.hpp" #include "gc/shared/stringdedup/stringDedup.hpp" #include "gc/shared/tlab_globals.hpp" #include "jvm.h" @@ -502,7 +503,6 @@ static SpecialFlag const special_jvm_flags[] = { { "RequireSharedSpaces", JDK_Version::jdk(18), JDK_Version::jdk(19), JDK_Version::undefined() }, { "UseSharedSpaces", JDK_Version::jdk(18), JDK_Version::jdk(19), JDK_Version::undefined() }, { "DontYieldALot", JDK_Version::jdk(23), JDK_Version::jdk(24), JDK_Version::jdk(25) }, - { "OldSize", JDK_Version::jdk(23), JDK_Version::jdk(24), JDK_Version::jdk(25) }, { "PreserveAllAnnotations", JDK_Version::jdk(23), JDK_Version::jdk(24), JDK_Version::jdk(25) }, { "UseNotificationThread", JDK_Version::jdk(23), JDK_Version::jdk(24), JDK_Version::jdk(25) }, { "UseEmptySlotsInSupers", JDK_Version::jdk(23), JDK_Version::jdk(24), JDK_Version::jdk(25) }, @@ -513,11 +513,14 @@ static SpecialFlag const special_jvm_flags[] = { { "MetaspaceReclaimPolicy", JDK_Version::undefined(), JDK_Version::jdk(21), JDK_Version::undefined() }, + { "OldSize", JDK_Version::jdk(23), JDK_Version::jdk(24), JDK_Version::jdk(25) }, #if defined(X86) { "UseRTMLocking", JDK_Version::jdk(23), JDK_Version::jdk(24), JDK_Version::jdk(25) }, { "UseRTMDeopt", JDK_Version::jdk(23), JDK_Version::jdk(24), JDK_Version::jdk(25) }, { "RTMRetryCount", JDK_Version::jdk(23), JDK_Version::jdk(24), JDK_Version::jdk(25) }, #endif // X86 + + { "HeapFirstMaximumCompactionCount", JDK_Version::undefined(), JDK_Version::jdk(24), JDK_Version::jdk(25) }, #ifdef ASSERT { "DummyObsoleteTestFlag", JDK_Version::undefined(), JDK_Version::jdk(18), JDK_Version::undefined() }, #endif @@ -748,7 +751,7 @@ static bool set_bool_flag(JVMFlag* flag, bool value, JVMFlagOrigin origin) { static bool set_fp_numeric_flag(JVMFlag* flag, const char* value, JVMFlagOrigin origin) { // strtod allows leading whitespace, but our flag format does not. - if (*value == '\0' || isspace(*value)) { + if (*value == '\0' || isspace((unsigned char) *value)) { return false; } char* end; @@ -1178,13 +1181,13 @@ bool Arguments::process_settings_file(const char* file_name, bool should_exist, if (c == '\n') in_comment = false; } else { if (c == '#') in_comment = true; - else if (!isspace(c)) { + else if (!isspace((unsigned char) c)) { in_white_space = false; token[pos++] = checked_cast(c); } } } else { - if (c == '\n' || (!in_quote && isspace(c))) { + if (c == '\n' || (!in_quote && isspace((unsigned char) c))) { // token ends at newline, or at unquoted whitespace // this allows a way to include spaces in string-valued options token[pos] = '\0'; @@ -3146,7 +3149,7 @@ jint Arguments::parse_options_buffer(const char* name, char* buffer, const size_ // parse all options while (rd < buffer_end) { // skip leading white space from the input string - while (rd < buffer_end && isspace(*rd)) { + while (rd < buffer_end && isspace((unsigned char) *rd)) { rd++; } @@ -3159,7 +3162,7 @@ jint Arguments::parse_options_buffer(const char* name, char* buffer, const size_ // Tokens are strings of non white space characters separated // by one or more white spaces. - while (rd < buffer_end && !isspace(*rd)) { + while (rd < buffer_end && !isspace((unsigned char) *rd)) { if (*rd == '\'' || *rd == '"') { // handle a quoted string int quote = *rd; // matching quote to look for rd++; // don't copy open quote @@ -3734,6 +3737,13 @@ jint Arguments::apply_ergo() { } #endif // COMPILER2_OR_JVMCI + if (log_is_enabled(Info, perf, class, link)) { + if (!UsePerfData) { + warning("Disabling -Xlog:perf+class+link since UsePerfData is turned off."); + LogConfiguration::configure_stdout(LogLevel::Off, false, LOG_TAGS(perf, class, link)); + } + } + if (FLAG_IS_CMDLINE(DiagnoseSyncOnValueBasedClasses)) { if (DiagnoseSyncOnValueBasedClasses == ObjectSynchronizer::LOG_WARNING && !log_is_enabled(Info, valuebasedclasses)) { LogConfiguration::configure_stdout(LogLevel::Info, true, LOG_TAGS(valuebasedclasses)); diff --git a/src/hotspot/share/runtime/java.cpp b/src/hotspot/share/runtime/java.cpp index b861002e9f6..b97ec7db755 100644 --- a/src/hotspot/share/runtime/java.cpp +++ b/src/hotspot/share/runtime/java.cpp @@ -24,7 +24,9 @@ #include "precompiled.hpp" #include "cds/cds_globals.hpp" +#include "cds/classListWriter.hpp" #include "cds/dynamicArchive.hpp" +#include "classfile/classLoader.hpp" #include "classfile/classLoaderDataGraph.hpp" #include "classfile/javaClasses.hpp" #include "classfile/stringTable.hpp" @@ -46,6 +48,7 @@ #include "memory/oopFactory.hpp" #include "memory/resourceArea.hpp" #include "memory/universe.hpp" +#include "nmt/memMapPrinter.hpp" #include "nmt/memTracker.hpp" #include "oops/constantPool.hpp" #include "oops/generateOopMap.hpp" @@ -165,7 +168,6 @@ static void print_method_profiling_data() { } } - #ifndef PRODUCT // Statistics printing (method invocation histogram) @@ -373,6 +375,8 @@ void print_statistics() { } ThreadsSMRSupport::log_statistics(); + + ClassLoader::print_counters(tty); } // Note: before_exit() can be executed only once, if more than one threads @@ -448,6 +452,10 @@ void before_exit(JavaThread* thread, bool halt) { } #endif +#if INCLUDE_CDS + ClassListWriter::write_resolved_constants(); +#endif + // Hang forever on exit if we're reporting an error. if (ShowMessageBoxOnError && VMError::is_error_reported()) { os::infinite_sleep(); @@ -495,6 +503,9 @@ void before_exit(JavaThread* thread, bool halt) { if (DumpPerfMapAtExit) { CodeCache::write_perf_map(); } + if (PrintMemoryMapAtExit) { + MemMapPrinter::print_all_mappings(tty, false); + } #ifdef HAVE_GLIBC_MALLOC_HOOKS // SapMachine 2021-09-01: malloc-trace if (PrintMallocTraceAtExit) { diff --git a/src/hotspot/share/runtime/objectMonitor.cpp b/src/hotspot/share/runtime/objectMonitor.cpp index 178f3e97d71..ba463231592 100644 --- a/src/hotspot/share/runtime/objectMonitor.cpp +++ b/src/hotspot/share/runtime/objectMonitor.cpp @@ -1442,7 +1442,7 @@ bool ObjectMonitor::check_owner(TRAPS) { static inline bool is_excluded(const Klass* monitor_klass) { assert(monitor_klass != nullptr, "invariant"); NOT_JFR_RETURN_(false); - JFR_ONLY(return vmSymbols::jfr_chunk_rotation_monitor() == monitor_klass->name();) + JFR_ONLY(return vmSymbols::jdk_jfr_internal_HiddenWait() == monitor_klass->name();) } static void post_monitor_wait_event(EventJavaMonitorWait* event, diff --git a/src/hotspot/share/runtime/os.hpp b/src/hotspot/share/runtime/os.hpp index a6626c1389f..ce7a07d4c43 100644 --- a/src/hotspot/share/runtime/os.hpp +++ b/src/hotspot/share/runtime/os.hpp @@ -344,6 +344,7 @@ class os: AllStatic { static julong physical_memory(); static bool has_allocatable_memory_limit(size_t* limit); static bool is_server_class_machine(); + static size_t rss(); // Returns the id of the processor on which the calling thread is currently executing. // The returned value is guaranteed to be between 0 and (os::processor_count() - 1). diff --git a/src/hotspot/share/runtime/perfData.cpp b/src/hotspot/share/runtime/perfData.cpp index b195274dc01..5e78baad3ab 100644 --- a/src/hotspot/share/runtime/perfData.cpp +++ b/src/hotspot/share/runtime/perfData.cpp @@ -296,7 +296,7 @@ void PerfDataManager::add_item(PerfData* p, bool sampled) { _has_PerfData = true; } - assert(!_all->contains(p->name()), "duplicate name added"); + assert(!_all->contains(p->name()), "duplicate name added: %s", p->name()); // add to the list of all perf data items _all->append(p); @@ -527,8 +527,3 @@ PerfDataList* PerfDataList::clone() { return copy; } -PerfTraceTime::~PerfTraceTime() { - if (!UsePerfData) return; - _t.stop(); - _timerp->inc(_t.ticks()); -} diff --git a/src/hotspot/share/runtime/perfData.hpp b/src/hotspot/share/runtime/perfData.hpp index 103e2698d9b..1f75560715a 100644 --- a/src/hotspot/share/runtime/perfData.hpp +++ b/src/hotspot/share/runtime/perfData.hpp @@ -831,11 +831,20 @@ class PerfTraceTime : public StackObj { public: inline PerfTraceTime(PerfLongCounter* timerp) : _timerp(timerp) { - if (!UsePerfData) return; + if (!UsePerfData || timerp == nullptr) { return; } _t.start(); } - ~PerfTraceTime(); + const char* name() const { + assert(_timerp != nullptr, "sanity"); + return _timerp->name(); + } + + ~PerfTraceTime() { + if (!UsePerfData || !_t.is_active()) { return; } + _t.stop(); + _timerp->inc(_t.ticks()); + } }; /* The PerfTraceTimedEvent class is responsible for counting the @@ -864,7 +873,7 @@ class PerfTraceTimedEvent : public PerfTraceTime { public: inline PerfTraceTimedEvent(PerfLongCounter* timerp, PerfLongCounter* eventp): PerfTraceTime(timerp), _eventp(eventp) { - if (!UsePerfData) return; + if (!UsePerfData || timerp == nullptr) { return; } _eventp->inc(); } diff --git a/src/hotspot/share/runtime/sharedRuntime.cpp b/src/hotspot/share/runtime/sharedRuntime.cpp index 0a7f5c54676..f794a15ac07 100644 --- a/src/hotspot/share/runtime/sharedRuntime.cpp +++ b/src/hotspot/share/runtime/sharedRuntime.cpp @@ -23,6 +23,7 @@ */ #include "precompiled.hpp" +#include "classfile/classLoader.hpp" #include "classfile/javaClasses.inline.hpp" #include "classfile/stringTable.hpp" #include "classfile/vmClasses.hpp" @@ -55,6 +56,7 @@ #include "prims/jvmtiThreadState.hpp" #include "prims/methodHandles.hpp" #include "prims/nativeLookup.hpp" +#include "runtime/arguments.hpp" #include "runtime/atomic.hpp" #include "runtime/frame.inline.hpp" #include "runtime/handles.inline.hpp" @@ -63,6 +65,7 @@ #include "runtime/java.hpp" #include "runtime/javaCalls.hpp" #include "runtime/jniHandles.inline.hpp" +#include "runtime/perfData.hpp" #include "runtime/sharedRuntime.hpp" #include "runtime/stackWatermarkSet.hpp" #include "runtime/stubRoutines.hpp" @@ -2570,6 +2573,9 @@ AdapterHandlerEntry* AdapterHandlerLibrary::create_adapter(AdapterBlob*& new_ada int total_args_passed, BasicType* sig_bt, bool allocate_code_blob) { + if (log_is_enabled(Info, perf, class, link)) { + ClassLoader::perf_method_adapters_count()->inc(); + } // StubRoutines::_final_stubs_code is initialized after this function can be called. As a result, // VerifyAdapterCalls and VerifyAdapterSharing can fail if we re-use code that generated prior diff --git a/src/hotspot/share/runtime/statSampler.cpp b/src/hotspot/share/runtime/statSampler.cpp index 5d2e102ada4..5fd038bf845 100644 --- a/src/hotspot/share/runtime/statSampler.cpp +++ b/src/hotspot/share/runtime/statSampler.cpp @@ -228,6 +228,19 @@ void StatSampler::add_property_constant(CounterNS name_space, const char* name, add_property_constant(name_space, name, Arguments::get_property(name), CHECK); } +/* + * Adds a string constant of the given property. Retrieves the value via + * Arguments::get_property() and asserts the value for the does not differ from + * the value retrievable from System.getProperty() + */ +void StatSampler::add_optional_property_constant(CounterNS name_space, const char* name, TRAPS) { + const char* value = Arguments::get_property(name); + + if (value != nullptr) { + add_property_constant(name_space, name, value, CHECK); + } +} + /* * Method to create PerfStringConstants containing the values of various * system properties. Constants are created from information known to HotSpot, @@ -260,6 +273,10 @@ void StatSampler::create_system_property_instrumentation(TRAPS) { add_property_constant(JAVA_PROPERTY, "java.library.path", CHECK); add_property_constant(JAVA_PROPERTY, "java.class.path", CHECK); add_property_constant(JAVA_PROPERTY, "java.home", CHECK); + + add_optional_property_constant(JAVA_PROPERTY, "jdk.module.path", CHECK); + add_optional_property_constant(JAVA_PROPERTY, "jdk.module.upgrade.path", CHECK); + add_optional_property_constant(JAVA_PROPERTY, "jdk.module.main", CHECK); } /* diff --git a/src/hotspot/share/runtime/statSampler.hpp b/src/hotspot/share/runtime/statSampler.hpp index a26f9743e0c..00daa5d95c0 100644 --- a/src/hotspot/share/runtime/statSampler.hpp +++ b/src/hotspot/share/runtime/statSampler.hpp @@ -53,6 +53,7 @@ class StatSampler : AllStatic { static void sample_data(PerfDataList* list); static void assert_system_property(const char* name, const char* value, TRAPS); static void add_property_constant(CounterNS name_space, const char* name, TRAPS); + static void add_optional_property_constant(CounterNS name_space, const char* name, TRAPS); static void add_property_constant(CounterNS name_space, const char* name, const char* value, TRAPS); static void create_system_property_instrumentation(TRAPS); diff --git a/src/hotspot/share/runtime/threads.cpp b/src/hotspot/share/runtime/threads.cpp index 5894a03a16f..29046a6f367 100644 --- a/src/hotspot/share/runtime/threads.cpp +++ b/src/hotspot/share/runtime/threads.cpp @@ -851,6 +851,12 @@ jint Threads::create_vm(JavaVMInitArgs* args, bool* canTryAgain) { MetaspaceShared::preload_and_dump(); } + if (log_is_enabled(Info, perf, class, link)) { + LogStreamHandle(Info, perf, class, link) log; + log.print_cr("At VM initialization completion:"); + ClassLoader::print_counters(&log); + } + return JNI_OK; } @@ -1022,7 +1028,6 @@ void Threads::add(JavaThread* p, bool force_daemon) { p->set_on_thread_list(); _number_of_threads++; - oop threadObj = p->threadObj(); bool daemon = true; // Bootstrapping problem: threadObj can be null for initial @@ -1360,8 +1365,11 @@ void Threads::print_on(outputStream* st, bool print_stacks, if (p->is_vthread_mounted()) { const oop vt = p->vthread(); assert(vt != nullptr, "vthread should not be null when vthread is mounted"); - st->print_cr(" Mounted virtual thread \"%s\" #" INT64_FORMAT, JavaThread::name_for(vt), (int64_t)java_lang_Thread::thread_id(vt)); - p->print_vthread_stack_on(st); + // JavaThread._vthread can refer to the carrier thread. Print only if _vthread refers to a virtual thread. + if (vt != thread_oop) { + st->print_cr(" Mounted virtual thread #" INT64_FORMAT, (int64_t)java_lang_Thread::thread_id(vt)); + p->print_vthread_stack_on(st); + } } } } diff --git a/src/hotspot/share/sanitizers/ub.hpp b/src/hotspot/share/sanitizers/ub.hpp new file mode 100644 index 00000000000..23e8ef4576c --- /dev/null +++ b/src/hotspot/share/sanitizers/ub.hpp @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024 SAP SE. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#ifndef SHARE_SANITIZERS_UB_HPP +#define SHARE_SANITIZERS_UB_HPP + +// ATTRIBUTE_NO_UBSAN +// +// Function attribute which informs the compiler to disable UBSan checks in the +// following function or method. +// Useful if the function or method is known to do something special or even 'dangerous', for +// example causing desired signals/crashes. +#if defined(__clang__) || defined(__GNUC__) +#define ATTRIBUTE_NO_UBSAN __attribute__((no_sanitize("undefined"))) +#endif + +#ifndef ATTRIBUTE_NO_UBSAN +#define ATTRIBUTE_NO_UBSAN +#endif + +#endif // SHARE_SANITIZERS_UB_HPP diff --git a/src/hotspot/share/services/diagnosticCommand.cpp b/src/hotspot/share/services/diagnosticCommand.cpp index adc16ecf23a..a8fb55c76b1 100644 --- a/src/hotspot/share/services/diagnosticCommand.cpp +++ b/src/hotspot/share/services/diagnosticCommand.cpp @@ -1243,11 +1243,11 @@ SystemDumpMapDCmd::SystemDumpMapDCmd(outputStream* output, bool heap) : void SystemDumpMapDCmd::execute(DCmdSource source, TRAPS) { stringStream defaultname; const char* name = nullptr; - if (::strcmp(default_filename, _filename.value()) == 0) { + if (_filename.is_set()) { + name = _filename.value(); + } else { defaultname.print("vm_memory_map_%d.txt", os::current_process_id()); name = defaultname.base(); - } else { - name = _filename.value(); } fileStream fs(name); if (fs.is_open()) { diff --git a/src/hotspot/share/services/diagnosticFramework.cpp b/src/hotspot/share/services/diagnosticFramework.cpp index 006c08cb63f..984122f2777 100644 --- a/src/hotspot/share/services/diagnosticFramework.cpp +++ b/src/hotspot/share/services/diagnosticFramework.cpp @@ -45,7 +45,7 @@ CmdLine::CmdLine(const char* line, size_t len, bool no_command_name) line_end = &line[len]; // Skip whitespace in the beginning of the line. - while (_cmd < line_end && isspace((int) _cmd[0])) { + while (_cmd < line_end && isspace((unsigned char) _cmd[0])) { _cmd++; } cmd_end = _cmd; @@ -55,7 +55,7 @@ CmdLine::CmdLine(const char* line, size_t len, bool no_command_name) _cmd_len = 0; } else { // Look for end of the command name - while (cmd_end < line_end && !isspace((int) cmd_end[0])) { + while (cmd_end < line_end && !isspace((unsigned char) cmd_end[0])) { cmd_end++; } _cmd_len = cmd_end - _cmd; diff --git a/src/hotspot/share/utilities/vmError.cpp b/src/hotspot/share/utilities/vmError.cpp index 61c281a2364..31ef1af6367 100644 --- a/src/hotspot/share/utilities/vmError.cpp +++ b/src/hotspot/share/utilities/vmError.cpp @@ -60,6 +60,7 @@ #include "runtime/vmOperations.hpp" #include "runtime/vmThread.hpp" #include "runtime/vm_version.hpp" +#include "sanitizers/ub.hpp" #include "utilities/debug.hpp" #include "utilities/decoder.hpp" #include "utilities/defaultStream.hpp" @@ -2155,9 +2156,7 @@ typedef void (*voidfun_t)(); // compared to one generated with raise (asynchronous vs synchronous). See JDK-8065895. volatile int sigfpe_int = 0; -#if defined(__clang__) || defined(__GNUC__) -__attribute__((no_sanitize("undefined"))) -#endif +ATTRIBUTE_NO_UBSAN static void ALWAYSINLINE crash_with_sigfpe() { // generate a native synchronous SIGFPE where possible; diff --git a/src/java.base/share/classes/java/lang/Class.java b/src/java.base/share/classes/java/lang/Class.java index 4bc7ccab834..5b378077d56 100644 --- a/src/java.base/share/classes/java/lang/Class.java +++ b/src/java.base/share/classes/java/lang/Class.java @@ -4720,6 +4720,7 @@ public Optional describeConstable() { * * @since 15 * @see MethodHandles.Lookup#defineHiddenClass + * @see Class##hiddenClasses Hidden Classes */ @IntrinsicCandidate public native boolean isHidden(); diff --git a/src/java.base/share/classes/java/lang/invoke/ClassSpecializer.java b/src/java.base/share/classes/java/lang/invoke/ClassSpecializer.java index a5eb681fb88..cca05a906ff 100644 --- a/src/java.base/share/classes/java/lang/invoke/ClassSpecializer.java +++ b/src/java.base/share/classes/java/lang/invoke/ClassSpecializer.java @@ -25,13 +25,12 @@ package java.lang.invoke; -import jdk.internal.loader.BootLoader; -import jdk.internal.org.objectweb.asm.ClassWriter; -import jdk.internal.org.objectweb.asm.FieldVisitor; -import jdk.internal.org.objectweb.asm.MethodVisitor; -import jdk.internal.vm.annotation.Stable; -import sun.invoke.util.BytecodeName; - +import java.lang.classfile.*; +import java.lang.classfile.attribute.ExceptionsAttribute; +import java.lang.classfile.attribute.SourceFileAttribute; +import java.lang.constant.ClassDesc; +import java.lang.constant.MethodTypeDesc; +import java.lang.invoke.LambdaForm.BasicType; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Modifier; @@ -42,12 +41,19 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.function.Function; -import static java.lang.invoke.LambdaForm.*; +import jdk.internal.constant.MethodTypeDescImpl; +import jdk.internal.constant.ReferenceClassDescImpl; +import jdk.internal.loader.BootLoader; +import jdk.internal.vm.annotation.Stable; +import sun.invoke.util.BytecodeName; +import sun.invoke.util.Wrapper; + +import static java.lang.classfile.ClassFile.*; +import static java.lang.constant.ConstantDescs.*; import static java.lang.invoke.MethodHandleNatives.Constants.REF_getStatic; import static java.lang.invoke.MethodHandleNatives.Constants.REF_putStatic; import static java.lang.invoke.MethodHandleStatics.*; import static java.lang.invoke.MethodHandles.Lookup.IMPL_LOOKUP; -import static jdk.internal.org.objectweb.asm.Opcodes.*; /** * Class specialization code. @@ -57,6 +63,10 @@ */ /*non-public*/ abstract class ClassSpecializer.SpeciesData> { + + private static final ClassDesc CD_LambdaForm = ReferenceClassDescImpl.ofValidated("Ljava/lang/invoke/LambdaForm;"); + private static final ClassDesc CD_BoundMethodHandle = ReferenceClassDescImpl.ofValidated("Ljava/lang/invoke/BoundMethodHandle;"); + private final Class topClass; private final Class keyType; private final Class metaType; @@ -404,7 +414,7 @@ protected String deriveTypeString() { buf.append(basicType.basicTypeChar()); } else { buf.append('V'); - end.append(classSig(type)); + end.append(type.descriptorString()); } } String typeString; @@ -572,8 +582,9 @@ Class generateConcreteSpeciesCode(String className, ClassSpecialize } // These are named like constants because there is only one per specialization scheme: - private final String SPECIES_DATA = classBCName(metaType); - private final String SPECIES_DATA_SIG = classSig(SPECIES_DATA); + + private final ClassDesc CD_SPECIES_DATA = classDesc(metaType); + private final MethodTypeDesc MTD_SPECIES_DATA = MethodTypeDescImpl.ofValidated(CD_SPECIES_DATA); private final String SPECIES_DATA_NAME = sdAccessor.getName(); private final int SPECIES_DATA_MODS = sdAccessor.getModifiers(); private final List TRANSFORM_NAMES; // derived from transformMethods @@ -595,268 +606,207 @@ Class generateConcreteSpeciesCode(String className, ClassSpecialize TRANSFORM_TYPES = List.of(tts.toArray(new MethodType[0])); TRANSFORM_MODS = List.of(tms.toArray(new Integer[0])); } + private static final MethodTypeDesc MTD_TRANFORM_HELPER = MethodTypeDescImpl.ofValidated(CD_MethodHandle, CD_int); private static final int ACC_PPP = ACC_PUBLIC | ACC_PRIVATE | ACC_PROTECTED; /*non-public*/ byte[] generateConcreteSpeciesCodeFile(String className0, ClassSpecializer.SpeciesData speciesData) { - final String className = classBCName(className0); - final String superClassName = classBCName(speciesData.deriveSuperClass()); - - final ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS + ClassWriter.COMPUTE_FRAMES); - final int NOT_ACC_PUBLIC = 0; // not ACC_PUBLIC - cw.visit(CLASSFILE_VERSION, NOT_ACC_PUBLIC + ACC_FINAL + ACC_SUPER, className, null, superClassName, null); - - final String sourceFile = className.substring(className.lastIndexOf('.')+1); - cw.visitSource(sourceFile, null); - - // emit static types and BMH_SPECIES fields - FieldVisitor fw = cw.visitField(NOT_ACC_PUBLIC + ACC_STATIC, sdFieldName, SPECIES_DATA_SIG, null, null); - fw.visitAnnotation(STABLE_SIG, true); - fw.visitEnd(); - - // handy holder for dealing with groups of typed values (ctor arguments and fields) - class Var { - final int index; - final String name; - final Class type; - final String desc; - final BasicType basicType; - final int slotIndex; - Var(int index, int slotIndex) { - this.index = index; - this.slotIndex = slotIndex; - name = null; type = null; desc = null; - basicType = BasicType.V_TYPE; - } - Var(String name, Class type, Var prev) { - int slotIndex = prev.nextSlotIndex(); - int index = prev.nextIndex(); - if (name == null) name = "x"; - if (name.endsWith("#")) - name = name.substring(0, name.length()-1) + index; - assert(!type.equals(void.class)); - String desc = classSig(type); - BasicType basicType = BasicType.basicType(type); - this.index = index; - this.name = name; - this.type = type; - this.desc = desc; - this.basicType = basicType; - this.slotIndex = slotIndex; - } - Var lastOf(List vars) { - int n = vars.size(); - return (n == 0 ? this : vars.get(n-1)); - } - List fromTypes(List types) { - Var prev = this; - ArrayList result = new ArrayList<>(types.size()); - int i = 0; - for (X x : types) { - String vn = name; - Class vt; - if (x instanceof Class cl) { - vt = cl; - // make the names friendlier if debugging - assert((vn = vn + "_" + (i++)) != null); - } else { - @SuppressWarnings("unchecked") - Var v = (Var) x; - vn = v.name; - vt = v.type; + final ClassDesc classDesc = ClassDesc.of(className0); + final ClassDesc superClassDesc = classDesc(speciesData.deriveSuperClass()); + return ClassFile.of().build(classDesc, clb -> { + clb.withFlags(ACC_FINAL | ACC_SUPER) + .withSuperclass(superClassDesc) + .with(SourceFileAttribute.of(classDesc.displayName())) + + // emit static types and BMH_SPECIES fields + .withField(sdFieldName, CD_SPECIES_DATA, ACC_STATIC); + + // handy holder for dealing with groups of typed values (ctor arguments and fields) + class Var { + final int index; + final String name; + final Class type; + final ClassDesc desc; + final BasicType basicType; + final int slotIndex; + Var(int index, int slotIndex) { + this.index = index; + this.slotIndex = slotIndex; + name = null; type = null; desc = null; + basicType = BasicType.V_TYPE; + } + Var(String name, Class type, Var prev) { + int slotIndex = prev.nextSlotIndex(); + int index = prev.nextIndex(); + if (name == null) name = "x"; + if (name.endsWith("#")) + name = name.substring(0, name.length()-1) + index; + assert(!type.equals(void.class)); + this.index = index; + this.name = name; + this.type = type; + this.desc = classDesc(type); + this.basicType = BasicType.basicType(type); + this.slotIndex = slotIndex; + } + Var lastOf(List vars) { + int n = vars.size(); + return (n == 0 ? this : vars.get(n-1)); + } + List fromTypes(List types) { + Var prev = this; + ArrayList result = new ArrayList<>(types.size()); + int i = 0; + for (X x : types) { + String vn = name; + Class vt; + if (x instanceof Class cl) { + vt = cl; + // make the names friendlier if debugging + assert((vn = vn + "_" + (i++)) != null); + } else { + @SuppressWarnings("unchecked") + Var v = (Var) x; + vn = v.name; + vt = v.type; + } + prev = new Var(vn, vt, prev); + result.add(prev); } - prev = new Var(vn, vt, prev); - result.add(prev); + return result; } - return result; - } - int slotSize() { return basicType.basicTypeSlots(); } - int nextIndex() { return index + (slotSize() == 0 ? 0 : 1); } - int nextSlotIndex() { return slotIndex >= 0 ? slotIndex + slotSize() : slotIndex; } - boolean isInHeap() { return slotIndex < 0; } - void emitVarInstruction(int asmop, MethodVisitor mv) { - if (asmop == ALOAD) - asmop = typeLoadOp(basicType.basicTypeChar()); - else - throw new AssertionError("bad op="+asmop+" for desc="+desc); - mv.visitVarInsn(asmop, slotIndex); - } - public void emitFieldInsn(int asmop, MethodVisitor mv) { - mv.visitFieldInsn(asmop, className, name, desc); - } - } - - final Var NO_THIS = new Var(0, 0), - AFTER_THIS = new Var(0, 1), - IN_HEAP = new Var(0, -1); - - // figure out the field types - final List> fieldTypes = speciesData.fieldTypes(); - final List fields = new ArrayList<>(fieldTypes.size()); - { - Var nextF = IN_HEAP; - for (Class ft : fieldTypes) { - String fn = chooseFieldName(ft, nextF.nextIndex()); - nextF = new Var(fn, ft, nextF); - fields.add(nextF); + int slotSize() { return basicType.basicTypeSlots(); } + int nextIndex() { return index + (slotSize() == 0 ? 0 : 1); } + int nextSlotIndex() { return slotIndex >= 0 ? slotIndex + slotSize() : slotIndex; } + boolean isInHeap() { return slotIndex < 0; } + void emitLoadInstruction(CodeBuilder cob) { + cob.loadLocal(basicType.btKind, slotIndex); + } } - } - // emit bound argument fields - for (Var field : fields) { - cw.visitField(ACC_FINAL, field.name, field.desc, null, null).visitEnd(); - } - - MethodVisitor mv; - - // emit implementation of speciesData() - mv = cw.visitMethod((SPECIES_DATA_MODS & ACC_PPP) + ACC_FINAL, - SPECIES_DATA_NAME, "()" + SPECIES_DATA_SIG, null, null); - mv.visitCode(); - mv.visitFieldInsn(GETSTATIC, className, sdFieldName, SPECIES_DATA_SIG); - mv.visitInsn(ARETURN); - mv.visitMaxs(0, 0); - mv.visitEnd(); - - // figure out the constructor arguments - MethodType superCtorType = ClassSpecializer.this.baseConstructorType(); - MethodType thisCtorType = superCtorType.appendParameterTypes(fieldTypes); - - // emit constructor - { - mv = cw.visitMethod(ACC_PRIVATE, - "", methodSig(thisCtorType), null, null); - mv.visitCode(); - mv.visitVarInsn(ALOAD, 0); // this - - final List ctorArgs = AFTER_THIS.fromTypes(superCtorType.parameterList()); - for (Var ca : ctorArgs) { - ca.emitVarInstruction(ALOAD, mv); + final Var NO_THIS = new Var(0, 0), + AFTER_THIS = new Var(0, 1), + IN_HEAP = new Var(0, -1); + + // figure out the field types + final List> fieldTypes = speciesData.fieldTypes(); + final List fields = new ArrayList<>(fieldTypes.size()); + { + Var nextF = IN_HEAP; + for (Class ft : fieldTypes) { + String fn = chooseFieldName(ft, nextF.nextIndex()); + nextF = new Var(fn, ft, nextF); + fields.add(nextF); + } } - // super(ca...) - mv.visitMethodInsn(INVOKESPECIAL, superClassName, - "", methodSig(superCtorType), false); - - // store down fields - Var lastFV = AFTER_THIS.lastOf(ctorArgs); - for (Var f : fields) { - // this.argL1 = argL1 - mv.visitVarInsn(ALOAD, 0); // this - lastFV = new Var(f.name, f.type, lastFV); - lastFV.emitVarInstruction(ALOAD, mv); - f.emitFieldInsn(PUTFIELD, mv); + // emit bound argument fields + for (Var field : fields) { + clb.withField(field.name, field.desc, ACC_FINAL); } - mv.visitInsn(RETURN); - mv.visitMaxs(0, 0); - mv.visitEnd(); - } + // emit implementation of speciesData() + clb.withMethodBody(SPECIES_DATA_NAME, MTD_SPECIES_DATA, (SPECIES_DATA_MODS & ACC_PPP) | ACC_FINAL, + cob -> cob.getstatic(classDesc, sdFieldName, CD_SPECIES_DATA) + .areturn()); - // emit make() ...factory method wrapping constructor - { - MethodType ftryType = thisCtorType.changeReturnType(topClass()); - mv = cw.visitMethod(NOT_ACC_PUBLIC + ACC_STATIC, - "make", methodSig(ftryType), null, null); - mv.visitCode(); - // make instance - mv.visitTypeInsn(NEW, className); - mv.visitInsn(DUP); - // load factory method arguments: ctarg... and arg... - for (Var v : NO_THIS.fromTypes(ftryType.parameterList())) { - v.emitVarInstruction(ALOAD, mv); - } + // figure out the constructor arguments + MethodType superCtorType = ClassSpecializer.this.baseConstructorType(); + MethodType thisCtorType = superCtorType.appendParameterTypes(fieldTypes); - // finally, invoke the constructor and return - mv.visitMethodInsn(INVOKESPECIAL, className, - "", methodSig(thisCtorType), false); - mv.visitInsn(ARETURN); - mv.visitMaxs(0, 0); - mv.visitEnd(); - } + // emit constructor + clb.withMethodBody(INIT_NAME, methodDesc(thisCtorType), ACC_PRIVATE, cob -> { + cob.aload(0); // this - // For each transform, emit the customized override of the transform method. - // This method mixes together some incoming arguments (from the transform's - // static type signature) with the field types themselves, and passes - // the resulting mish-mosh of values to a method handle produced by - // the species itself. (Typically this method handle is the factory - // method of this species or a related one.) - for (int whichtm = 0; whichtm < TRANSFORM_NAMES.size(); whichtm++) { - final String TNAME = TRANSFORM_NAMES.get(whichtm); - final MethodType TTYPE = TRANSFORM_TYPES.get(whichtm); - final int TMODS = TRANSFORM_MODS.get(whichtm); - mv = cw.visitMethod((TMODS & ACC_PPP) | ACC_FINAL, - TNAME, TTYPE.toMethodDescriptorString(), null, E_THROWABLE); - mv.visitCode(); - // return a call to the corresponding "transform helper", something like this: - // MY_SPECIES.transformHelper(whichtm).invokeBasic(ctarg, ..., argL0, ..., xarg) - mv.visitFieldInsn(GETSTATIC, className, - sdFieldName, SPECIES_DATA_SIG); - emitIntConstant(whichtm, mv); - mv.visitMethodInsn(INVOKEVIRTUAL, SPECIES_DATA, - "transformHelper", "(I)" + MH_SIG, false); - - List targs = AFTER_THIS.fromTypes(TTYPE.parameterList()); - List tfields = new ArrayList<>(fields); - // mix them up and load them for the transform helper: - List helperArgs = speciesData.deriveTransformHelperArguments(transformMethods.get(whichtm), whichtm, targs, tfields); - List> helperTypes = new ArrayList<>(helperArgs.size()); - for (Var ha : helperArgs) { - helperTypes.add(ha.basicType.basicTypeClass()); - if (ha.isInHeap()) { - assert(tfields.contains(ha)); - mv.visitVarInsn(ALOAD, 0); - ha.emitFieldInsn(GETFIELD, mv); - } else { - assert(targs.contains(ha)); - ha.emitVarInstruction(ALOAD, mv); + final List ctorArgs = AFTER_THIS.fromTypes(superCtorType.parameterList()); + for (Var ca : ctorArgs) { + ca.emitLoadInstruction(cob); } - } - - // jump into the helper (which is probably a factory method) - final Class rtype = TTYPE.returnType(); - final BasicType rbt = BasicType.basicType(rtype); - MethodType invokeBasicType = MethodType.methodType(rbt.basicTypeClass(), helperTypes); - mv.visitMethodInsn(INVOKEVIRTUAL, MH, - "invokeBasic", methodSig(invokeBasicType), false); - if (rbt == BasicType.L_TYPE) { - mv.visitTypeInsn(CHECKCAST, classBCName(rtype)); - mv.visitInsn(ARETURN); - } else { - throw newInternalError("NYI: transform of type "+rtype); - } - mv.visitMaxs(0, 0); - mv.visitEnd(); - } - cw.visitEnd(); - - return cw.toByteArray(); - } + // super(ca...) + cob.invokespecial(superClassDesc, INIT_NAME, methodDesc(superCtorType)); + + // store down fields + Var lastFV = AFTER_THIS.lastOf(ctorArgs); + for (Var f : fields) { + // this.argL1 = argL1 + cob.aload(0); // this + lastFV = new Var(f.name, f.type, lastFV); + lastFV.emitLoadInstruction(cob); + cob.putfield(classDesc, f.name, f.desc); + } - private int typeLoadOp(char t) { - return switch (t) { - case 'L' -> ALOAD; - case 'I' -> ILOAD; - case 'J' -> LLOAD; - case 'F' -> FLOAD; - case 'D' -> DLOAD; - default -> throw newInternalError("unrecognized type " + t); - }; - } + cob.return_(); + }); - private void emitIntConstant(int con, MethodVisitor mv) { - if (ICONST_M1 - ICONST_0 <= con && con <= ICONST_5 - ICONST_0) - mv.visitInsn(ICONST_0 + con); - else if (con == (byte) con) - mv.visitIntInsn(BIPUSH, con); - else if (con == (short) con) - mv.visitIntInsn(SIPUSH, con); - else { - mv.visitLdcInsn(con); - } + // emit make() ...factory method wrapping constructor + MethodType ftryType = thisCtorType.changeReturnType(topClass()); + clb.withMethodBody("make", methodDesc(ftryType), ACC_STATIC, cob -> { + // make instance + cob.new_(classDesc) + .dup(); + // load factory method arguments: ctarg... and arg... + for (Var v : NO_THIS.fromTypes(ftryType.parameterList())) { + v.emitLoadInstruction(cob); + } + // finally, invoke the constructor and return + cob.invokespecial(classDesc, INIT_NAME, methodDesc(thisCtorType)) + .areturn(); + }); + + // For each transform, emit the customized override of the transform method. + // This method mixes together some incoming arguments (from the transform's + // static type signature) with the field types themselves, and passes + // the resulting mish-mosh of values to a method handle produced by + // the species itself. (Typically this method handle is the factory + // method of this species or a related one.) + for (int i = 0; i < TRANSFORM_NAMES.size(); i++) { + final int whichtm = i; + final String TNAME = TRANSFORM_NAMES.get(whichtm); + final MethodType TTYPE = TRANSFORM_TYPES.get(whichtm); + final int TMODS = TRANSFORM_MODS.get(whichtm); + clb.withMethod(TNAME, methodDesc(TTYPE), (TMODS & ACC_PPP) | ACC_FINAL, mb -> { + mb.with(ExceptionsAttribute.ofSymbols(CD_Throwable)) + .withCode(cob -> { + // return a call to the corresponding "transform helper", something like this: + // MY_SPECIES.transformHelper(whichtm).invokeBasic(ctarg, ..., argL0, ..., xarg) + cob.getstatic(classDesc, sdFieldName, CD_SPECIES_DATA) + .loadConstant(whichtm) + .invokevirtual(CD_SPECIES_DATA, "transformHelper", MTD_TRANFORM_HELPER); + + List targs = AFTER_THIS.fromTypes(TTYPE.parameterList()); + List tfields = new ArrayList<>(fields); + // mix them up and load them for the transform helper: + List helperArgs = speciesData.deriveTransformHelperArguments(transformMethods.get(whichtm), whichtm, targs, tfields); + ClassDesc[] helperTypes = new ClassDesc[helperArgs.size()]; + for (int hi = 0; hi < helperTypes.length; hi++) { + Var ha = helperArgs.get(hi); + helperTypes[hi] = ha.basicType.basicTypeWrapper().basicClassDescriptor(); + if (ha.isInHeap()) { + assert(tfields.contains(ha)); + cob.aload(0); + cob.getfield(classDesc, ha.name, ha.desc); + } else { + assert(targs.contains(ha)); + ha.emitLoadInstruction(cob); + } + } + + // jump into the helper (which is probably a factory method) + final Class rtype = TTYPE.returnType(); + if (!rtype.isPrimitive()) { + cob.invokevirtual(CD_MethodHandle, "invokeBasic", MethodTypeDescImpl.ofValidated(CD_Object, helperTypes)) + .checkcast(classDesc(rtype)) + .areturn(); + } else { + throw newInternalError("NYI: transform of type "+rtype); + } + }); + }); + } + }); } // @@ -990,39 +940,25 @@ protected Factory makeFactory() { // Other misc helpers: - private static final String MH = "java/lang/invoke/MethodHandle"; - private static final String MH_SIG = "L" + MH + ";"; - private static final String STABLE = "jdk/internal/vm/annotation/Stable"; - private static final String STABLE_SIG = "L" + STABLE + ";"; - private static final String[] E_THROWABLE = new String[] { "java/lang/Throwable" }; - static { - assert(MH_SIG.equals(classSig(MethodHandle.class))); - assert(MH.equals(classBCName(MethodHandle.class))); - } - - static String methodSig(MethodType mt) { - return mt.toMethodDescriptorString(); - } - static String classSig(Class cls) { - if (cls.isPrimitive() || cls.isArray()) - return MethodType.methodType(cls).toMethodDescriptorString().substring(2); - return classSig(classBCName(cls)); - } - static String classSig(String bcName) { - assert(bcName.indexOf('.') < 0); - assert(!bcName.endsWith(";")); - assert(!bcName.startsWith("[")); - return "L" + bcName + ";"; - } - static String classBCName(Class cls) { - return classBCName(className(cls)); - } static String classBCName(String str) { assert(str.indexOf('/') < 0) : str; return str.replace('.', '/'); } - static String className(Class cls) { - assert(!cls.isArray() && !cls.isPrimitive()); - return cls.getName(); + + static ClassDesc classDesc(Class cls) { + return cls.isPrimitive() ? Wrapper.forPrimitiveType(cls).basicClassDescriptor() + : cls == Object.class ? CD_Object + : cls == MethodType.class ? CD_MethodType + : cls == LambdaForm.class ? CD_LambdaForm + : cls == BoundMethodHandle.class ? CD_BoundMethodHandle + : ReferenceClassDescImpl.ofValidated(cls.descriptorString()); + } + + static MethodTypeDesc methodDesc(MethodType mt) { + var params = new ClassDesc[mt.parameterCount()]; + for (int i = 0; i < params.length; i++) { + params[i] = classDesc(mt.parameterType(i)); + } + return MethodTypeDescImpl.ofValidated(classDesc(mt.returnType()), params); } } diff --git a/src/java.base/share/classes/java/lang/invoke/GenerateJLIClassesHelper.java b/src/java.base/share/classes/java/lang/invoke/GenerateJLIClassesHelper.java index 46eeb67de54..1e3f465f97d 100644 --- a/src/java.base/share/classes/java/lang/invoke/GenerateJLIClassesHelper.java +++ b/src/java.base/share/classes/java/lang/invoke/GenerateJLIClassesHelper.java @@ -25,10 +25,11 @@ package java.lang.invoke; -import jdk.internal.org.objectweb.asm.ClassWriter; -import jdk.internal.org.objectweb.asm.Opcodes; import sun.invoke.util.Wrapper; +import java.lang.classfile.ClassFile; +import java.lang.classfile.attribute.SourceFileAttribute; +import java.lang.constant.ClassDesc; import java.util.ArrayList; import java.util.HashSet; import java.util.Map; @@ -38,10 +39,10 @@ import java.util.TreeSet; import java.util.stream.Stream; +import static java.lang.classfile.ClassFile.*; import static java.lang.invoke.LambdaForm.BasicType.*; -import static java.lang.invoke.MethodHandleStatics.CLASSFILE_VERSION; -import static java.lang.invoke.MethodTypeForm.*; import static java.lang.invoke.LambdaForm.Kind.*; +import static java.lang.invoke.MethodTypeForm.*; /** * Helper class to assist the GenerateJLIClassesPlugin to get access to @@ -557,19 +558,14 @@ static byte[] generateInvokersHolderClassBytes(String className, * a class with a specified name. */ private static byte[] generateCodeBytesForLFs(String className, String[] names, LambdaForm[] forms) { - ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS + ClassWriter.COMPUTE_FRAMES); - cw.visit(CLASSFILE_VERSION, Opcodes.ACC_PRIVATE + Opcodes.ACC_FINAL + Opcodes.ACC_SUPER, - className, null, InvokerBytecodeGenerator.INVOKER_SUPER_NAME, null); - cw.visitSource(className.substring(className.lastIndexOf('/') + 1), null); - - for (int i = 0; i < forms.length; i++) { - InvokerBytecodeGenerator g - = new InvokerBytecodeGenerator(className, names[i], forms[i], forms[i].methodType()); - g.setClassWriter(cw); - g.addMethod(); - } - - return cw.toByteArray(); + return ClassFile.of().build(ClassDesc.ofInternalName(className), clb -> { + clb.withFlags(ACC_PRIVATE | ACC_FINAL | ACC_SUPER) + .withSuperclass(InvokerBytecodeGenerator.INVOKER_SUPER_DESC) + .with(SourceFileAttribute.of(className.substring(className.lastIndexOf('/') + 1))); + for (int i = 0; i < forms.length; i++) { + new InvokerBytecodeGenerator(className, names[i], forms[i], forms[i].methodType()).addMethod(clb); + } + }); } private static LambdaForm makeReinvokerFor(MethodType type) { diff --git a/src/java.base/share/classes/java/lang/invoke/InnerClassLambdaMetafactory.java b/src/java.base/share/classes/java/lang/invoke/InnerClassLambdaMetafactory.java index b4afbc098c9..ce2547710a5 100644 --- a/src/java.base/share/classes/java/lang/invoke/InnerClassLambdaMetafactory.java +++ b/src/java.base/share/classes/java/lang/invoke/InnerClassLambdaMetafactory.java @@ -26,23 +26,40 @@ package java.lang.invoke; import jdk.internal.misc.CDS; -import jdk.internal.org.objectweb.asm.*; import jdk.internal.util.ClassFileDumper; -import sun.invoke.util.BytecodeDescriptor; import sun.invoke.util.VerifyAccess; import sun.security.action.GetBooleanAction; import java.io.Serializable; -import java.lang.constant.ConstantDescs; +import java.lang.classfile.ClassBuilder; +import java.lang.classfile.ClassFile; +import java.lang.classfile.CodeBuilder; +import java.lang.classfile.FieldBuilder; +import java.lang.classfile.MethodBuilder; +import java.lang.classfile.Opcode; +import java.lang.classfile.TypeKind; +import java.lang.constant.ClassDesc; +import java.lang.constant.DynamicConstantDesc; +import java.lang.constant.MethodTypeDesc; import java.lang.reflect.Modifier; import java.util.LinkedHashSet; +import java.util.List; import java.util.Set; - -import static java.lang.invoke.MethodHandleStatics.CLASSFILE_VERSION; +import java.util.function.Consumer; + +import static java.lang.classfile.ClassFile.*; +import java.lang.classfile.attribute.ExceptionsAttribute; +import java.lang.classfile.constantpool.ClassEntry; +import java.lang.classfile.constantpool.ConstantPoolBuilder; +import java.lang.classfile.constantpool.MethodRefEntry; +import static java.lang.constant.ConstantDescs.*; import static java.lang.invoke.MethodHandles.Lookup.ClassOption.NESTMATE; import static java.lang.invoke.MethodHandles.Lookup.ClassOption.STRONG; import static java.lang.invoke.MethodType.methodType; -import static jdk.internal.org.objectweb.asm.Opcodes.*; +import jdk.internal.constant.ConstantUtils; +import jdk.internal.constant.MethodTypeDescImpl; +import jdk.internal.constant.ReferenceClassDescImpl; +import sun.invoke.util.Wrapper; /** * Lambda metafactory implementation which dynamically creates an @@ -51,42 +68,29 @@ * @see LambdaMetafactory */ /* package */ final class InnerClassLambdaMetafactory extends AbstractValidatingLambdaMetafactory { - private static final String METHOD_DESCRIPTOR_VOID = Type.getMethodDescriptor(Type.VOID_TYPE); - private static final String JAVA_LANG_OBJECT = "java/lang/Object"; - private static final String NAME_CTOR = ""; private static final String LAMBDA_INSTANCE_FIELD = "LAMBDA_INSTANCE$"; - - //Serialization support - private static final String NAME_SERIALIZED_LAMBDA = "java/lang/invoke/SerializedLambda"; - private static final String NAME_NOT_SERIALIZABLE_EXCEPTION = "java/io/NotSerializableException"; - private static final String DESCR_METHOD_WRITE_REPLACE = "()Ljava/lang/Object;"; - private static final String DESCR_METHOD_WRITE_OBJECT = "(Ljava/io/ObjectOutputStream;)V"; - private static final String DESCR_METHOD_READ_OBJECT = "(Ljava/io/ObjectInputStream;)V"; - - private static final String NAME_METHOD_WRITE_REPLACE = "writeReplace"; - private static final String NAME_METHOD_READ_OBJECT = "readObject"; - private static final String NAME_METHOD_WRITE_OBJECT = "writeObject"; - - private static final String DESCR_CLASS = "Ljava/lang/Class;"; - private static final String DESCR_STRING = "Ljava/lang/String;"; - private static final String DESCR_OBJECT = "Ljava/lang/Object;"; - private static final String DESCR_CTOR_SERIALIZED_LAMBDA - = "(" + DESCR_CLASS + DESCR_STRING + DESCR_STRING + DESCR_STRING + "I" - + DESCR_STRING + DESCR_STRING + DESCR_STRING + DESCR_STRING + "[" + DESCR_OBJECT + ")V"; - - private static final String DESCR_CTOR_NOT_SERIALIZABLE_EXCEPTION = "(Ljava/lang/String;)V"; - private static final String[] SER_HOSTILE_EXCEPTIONS = new String[] {NAME_NOT_SERIALIZABLE_EXCEPTION}; - private static final String[] EMPTY_STRING_ARRAY = new String[0]; + private static final ClassDesc[] EMPTY_CLASSDESC_ARRAY = ConstantUtils.EMPTY_CLASSDESC; + + // Static builders to avoid lambdas + record FieldFlags(int flags) implements Consumer { + @Override + public void accept(FieldBuilder fb) { + fb.withFlags(flags); + } + }; + record MethodBody(Consumer code) implements Consumer { + @Override + public void accept(MethodBuilder mb) { + mb.withCode(code); + } + }; // For dumping generated classes to disk, for debugging purposes private static final ClassFileDumper lambdaProxyClassFileDumper; private static final boolean disableEagerInitialization; - // condy to load implMethod from class data - private static final ConstantDynamic implMethodCondy; - static { // To dump the lambda proxy classes, set this system property: // -Djdk.invoke.LambdaMetafactory.dumpProxyClassFiles @@ -96,23 +100,18 @@ final String disableEagerInitializationKey = "jdk.internal.lambda.disableEagerInitialization"; disableEagerInitialization = GetBooleanAction.privilegedGetProperty(disableEagerInitializationKey); - - // condy to load implMethod from class data - MethodType classDataMType = methodType(Object.class, MethodHandles.Lookup.class, String.class, Class.class); - Handle classDataBsm = new Handle(H_INVOKESTATIC, Type.getInternalName(MethodHandles.class), "classData", - classDataMType.descriptorString(), false); - implMethodCondy = new ConstantDynamic(ConstantDescs.DEFAULT_NAME, MethodHandle.class.descriptorString(), classDataBsm); } // See context values in AbstractValidatingLambdaMetafactory - private final String implMethodClassName; // Name of type containing implementation "CC" + private final ClassDesc implMethodClassDesc; // Name of type containing implementation "CC" private final String implMethodName; // Name of implementation method "impl" - private final String implMethodDesc; // Type descriptor for implementation methods "(I)Ljava/lang/String;" + private final MethodTypeDesc implMethodDesc; // Type descriptor for implementation methods "(I)Ljava/lang/String;" private final MethodType constructorType; // Generated class constructor type "(CC)void" - private final ClassWriter cw; // ASM class writer + private final MethodTypeDesc constructorTypeDesc;// Type descriptor for the generated class constructor type "(CC)void" private final String[] argNames; // Generated names for the constructor arguments - private final String[] argDescs; // Type descriptors for the constructor arguments - private final String lambdaClassName; // Generated name for the generated class "X$$Lambda" + private final ClassDesc[] argDescs; // Type descriptors for the constructor arguments + private final String lambdaClassName; // Generated name for the generated class "X$$Lambda$1" + private final ClassDesc lambdaClassDesc; // Type descriptor for the generated class "X$$Lambda$1" private final boolean useImplMethodHandle; // use MethodHandle invocation instead of symbolic bytecode invocation /** @@ -168,11 +167,13 @@ public InnerClassLambdaMetafactory(MethodHandles.Lookup caller, super(caller, factoryType, interfaceMethodName, interfaceMethodType, implementation, dynamicMethodType, isSerializable, altInterfaces, altMethods); - implMethodClassName = implClass.getName().replace('.', '/'); + implMethodClassDesc = implClassDesc(implClass); implMethodName = implInfo.getName(); - implMethodDesc = implInfo.getMethodType().toMethodDescriptorString(); + implMethodDesc = methodDesc(implInfo.getMethodType()); constructorType = factoryType.changeReturnType(Void.TYPE); + constructorTypeDesc = methodDesc(constructorType); lambdaClassName = lambdaClassName(targetClass); + lambdaClassDesc = ClassDesc.ofInternalName(lambdaClassName); // If the target class invokes a protected method inherited from a // superclass in a different package, or does 'invokespecial', the // lambda class has no access to the resolved method, or does @@ -182,19 +183,19 @@ public InnerClassLambdaMetafactory(MethodHandles.Lookup caller, // situation by generating bridges in the target class) useImplMethodHandle = (Modifier.isProtected(implInfo.getModifiers()) && !VerifyAccess.isSamePackage(targetClass, implInfo.getDeclaringClass())) || - implKind == H_INVOKESPECIAL || - implKind == H_INVOKESTATIC && implClass.isHidden(); - cw = new ClassWriter(ClassWriter.COMPUTE_MAXS); + implKind == MethodHandleInfo.REF_invokeSpecial || + implKind == MethodHandleInfo.REF_invokeStatic && implClass.isHidden(); int parameterCount = factoryType.parameterCount(); if (parameterCount > 0) { argNames = new String[parameterCount]; - argDescs = new String[parameterCount]; + argDescs = new ClassDesc[parameterCount]; for (int i = 0; i < parameterCount; i++) { argNames[i] = "arg$" + (i + 1); - argDescs[i] = BytecodeDescriptor.unparse(factoryType.parameterType(i)); + argDescs[i] = classDesc(factoryType.parameterType(i)); } } else { - argNames = argDescs = EMPTY_STRING_ARRAY; + argNames = EMPTY_STRING_ARRAY; + argDescs = EMPTY_CLASSDESC_ARRAY; } } @@ -300,65 +301,63 @@ private Class spinInnerClass() throws LambdaConversionException { * is not found */ private Class generateInnerClass() throws LambdaConversionException { - String[] interfaceNames; - String interfaceName = interfaceClass.getName().replace('.', '/'); + List interfaces; + ClassDesc interfaceDesc = classDesc(interfaceClass); boolean accidentallySerializable = !isSerializable && Serializable.class.isAssignableFrom(interfaceClass); if (altInterfaces.length == 0) { - interfaceNames = new String[]{interfaceName}; + interfaces = List.of(interfaceDesc); } else { // Assure no duplicate interfaces (ClassFormatError) - Set itfs = LinkedHashSet.newLinkedHashSet(altInterfaces.length + 1); - itfs.add(interfaceName); + Set itfs = LinkedHashSet.newLinkedHashSet(altInterfaces.length + 1); + itfs.add(interfaceDesc); for (Class i : altInterfaces) { - itfs.add(i.getName().replace('.', '/')); + itfs.add(classDesc(i)); accidentallySerializable |= !isSerializable && Serializable.class.isAssignableFrom(i); } - interfaceNames = itfs.toArray(new String[itfs.size()]); + interfaces = List.copyOf(itfs); } + final boolean finalAccidentallySerializable = accidentallySerializable; + final byte[] classBytes = ClassFile.of().build(lambdaClassDesc, new Consumer() { + @Override + public void accept(ClassBuilder clb) { + clb.withFlags(ACC_SUPER | ACC_FINAL | ACC_SYNTHETIC) + .withInterfaceSymbols(interfaces); + // Generate final fields to be filled in by constructor + for (int i = 0; i < argDescs.length; i++) { + clb.withField(argNames[i], argDescs[i], new FieldFlags(ACC_PRIVATE | ACC_FINAL)); + } - cw.visit(CLASSFILE_VERSION, ACC_SUPER + ACC_FINAL + ACC_SYNTHETIC, - lambdaClassName, null, - JAVA_LANG_OBJECT, interfaceNames); - - // Generate final fields to be filled in by constructor - for (int i = 0; i < argDescs.length; i++) { - FieldVisitor fv = cw.visitField(ACC_PRIVATE + ACC_FINAL, - argNames[i], - argDescs[i], - null, null); - fv.visitEnd(); - } + generateConstructor(clb); - generateConstructor(); + if (factoryType.parameterCount() == 0 && disableEagerInitialization) { + generateClassInitializer(clb); + } - if (factoryType.parameterCount() == 0 && disableEagerInitialization) { - generateClassInitializer(); - } + // Forward the SAM method + clb.withMethod(interfaceMethodName, + methodDesc(interfaceMethodType), + ACC_PUBLIC, + forwardingMethod(interfaceMethodType)); + + // Forward the bridges + if (altMethods != null) { + for (MethodType mt : altMethods) { + clb.withMethod(interfaceMethodName, + methodDesc(mt), + ACC_PUBLIC | ACC_BRIDGE, + forwardingMethod(mt)); + } + } - // Forward the SAM method - MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, interfaceMethodName, - interfaceMethodType.toMethodDescriptorString(), null, null); - new ForwardingMethodGenerator(mv).generate(interfaceMethodType); - - // Forward the altMethods - if (altMethods != null) { - for (MethodType mt : altMethods) { - mv = cw.visitMethod(ACC_PUBLIC, interfaceMethodName, - mt.toMethodDescriptorString(), null, null); - new ForwardingMethodGenerator(mv).generate(mt); + if (isSerializable) + generateSerializationFriendlyMethods(clb); + else if (finalAccidentallySerializable) + generateSerializationHostileMethods(clb); } - } - - if (isSerializable) - generateSerializationFriendlyMethods(); - else if (accidentallySerializable) - generateSerializationHostileMethods(); - - cw.visitEnd(); + }); // Define the generated class in this VM. - final byte[] classBytes = cw.toByteArray(); try { // this class is linked at the indy callsite; so define a hidden nestmate var classdata = useImplMethodHandle? implementation : null; @@ -373,237 +372,214 @@ else if (accidentallySerializable) /** * Generate a static field and a static initializer that sets this field to an instance of the lambda */ - private void generateClassInitializer() { - String lambdaTypeDescriptor = factoryType.returnType().descriptorString(); + private void generateClassInitializer(ClassBuilder clb) { + ClassDesc lambdaTypeDescriptor = classDesc(factoryType.returnType()); // Generate the static final field that holds the lambda singleton - FieldVisitor fv = cw.visitField(ACC_PRIVATE | ACC_STATIC | ACC_FINAL, - LAMBDA_INSTANCE_FIELD, lambdaTypeDescriptor, null, null); - fv.visitEnd(); + clb.withField(LAMBDA_INSTANCE_FIELD, lambdaTypeDescriptor, new FieldFlags(ACC_PRIVATE | ACC_STATIC | ACC_FINAL)); // Instantiate the lambda and store it to the static final field - MethodVisitor clinit = cw.visitMethod(ACC_STATIC, "", "()V", null, null); - clinit.visitCode(); - - clinit.visitTypeInsn(NEW, lambdaClassName); - clinit.visitInsn(Opcodes.DUP); - assert factoryType.parameterCount() == 0; - clinit.visitMethodInsn(INVOKESPECIAL, lambdaClassName, NAME_CTOR, constructorType.toMethodDescriptorString(), false); - clinit.visitFieldInsn(PUTSTATIC, lambdaClassName, LAMBDA_INSTANCE_FIELD, lambdaTypeDescriptor); - - clinit.visitInsn(RETURN); - clinit.visitMaxs(-1, -1); - clinit.visitEnd(); + clb.withMethod(CLASS_INIT_NAME, MTD_void, ACC_STATIC, new MethodBody(new Consumer() { + @Override + public void accept(CodeBuilder cob) { + assert factoryType.parameterCount() == 0; + cob.new_(lambdaClassDesc) + .dup() + .invokespecial(lambdaClassDesc, INIT_NAME, constructorTypeDesc) + .putstatic(lambdaClassDesc, LAMBDA_INSTANCE_FIELD, lambdaTypeDescriptor) + .return_(); + } + })); } /** * Generate the constructor for the class */ - private void generateConstructor() { + private void generateConstructor(ClassBuilder clb) { // Generate constructor - MethodVisitor ctor = cw.visitMethod(ACC_PRIVATE, NAME_CTOR, - constructorType.toMethodDescriptorString(), null, null); - ctor.visitCode(); - ctor.visitVarInsn(ALOAD, 0); - ctor.visitMethodInsn(INVOKESPECIAL, JAVA_LANG_OBJECT, NAME_CTOR, - METHOD_DESCRIPTOR_VOID, false); - int parameterCount = factoryType.parameterCount(); - for (int i = 0, lvIndex = 0; i < parameterCount; i++) { - ctor.visitVarInsn(ALOAD, 0); - Class argType = factoryType.parameterType(i); - ctor.visitVarInsn(getLoadOpcode(argType), lvIndex + 1); - lvIndex += getParameterSize(argType); - ctor.visitFieldInsn(PUTFIELD, lambdaClassName, argNames[i], argDescs[i]); - } - ctor.visitInsn(RETURN); - // Maxs computed by ClassWriter.COMPUTE_MAXS, these arguments ignored - ctor.visitMaxs(-1, -1); - ctor.visitEnd(); + clb.withMethod(INIT_NAME, constructorTypeDesc, ACC_PRIVATE, + new MethodBody(new Consumer() { + @Override + public void accept(CodeBuilder cob) { + cob.aload(0) + .invokespecial(CD_Object, INIT_NAME, MTD_void); + int parameterCount = factoryType.parameterCount(); + for (int i = 0; i < parameterCount; i++) { + cob.aload(0); + Class argType = factoryType.parameterType(i); + cob.loadLocal(TypeKind.from(argType), cob.parameterSlot(i)); + cob.putfield(lambdaClassDesc, argNames[i], argDescs[i]); + } + cob.return_(); + } + })); + } + + private static class SerializationSupport { + // Serialization support + private static final ClassDesc CD_SerializedLambda = ReferenceClassDescImpl.ofValidated("Ljava/lang/invoke/SerializedLambda;"); + private static final ClassDesc CD_ObjectOutputStream = ReferenceClassDescImpl.ofValidated("Ljava/io/ObjectOutputStream;"); + private static final ClassDesc CD_ObjectInputStream = ReferenceClassDescImpl.ofValidated("Ljava/io/ObjectInputStream;"); + private static final MethodTypeDesc MTD_Object = MethodTypeDescImpl.ofValidated(CD_Object); + private static final MethodTypeDesc MTD_void_ObjectOutputStream = MethodTypeDescImpl.ofValidated(CD_void, CD_ObjectOutputStream); + private static final MethodTypeDesc MTD_void_ObjectInputStream = MethodTypeDescImpl.ofValidated(CD_void, CD_ObjectInputStream); + + private static final String NAME_METHOD_WRITE_REPLACE = "writeReplace"; + private static final String NAME_METHOD_READ_OBJECT = "readObject"; + private static final String NAME_METHOD_WRITE_OBJECT = "writeObject"; + + static final ClassDesc CD_NotSerializableException = ReferenceClassDescImpl.ofValidated("Ljava/io/NotSerializableException;"); + static final MethodTypeDesc MTD_CTOR_NOT_SERIALIZABLE_EXCEPTION = MethodTypeDescImpl.ofValidated(CD_void, CD_String); + static final MethodTypeDesc MTD_CTOR_SERIALIZED_LAMBDA = MethodTypeDescImpl.ofValidated(CD_void, + CD_Class, CD_String, CD_String, CD_String, CD_int, CD_String, CD_String, CD_String, CD_String, ReferenceClassDescImpl.ofValidated("[Ljava/lang/Object;")); + } /** * Generate a writeReplace method that supports serialization */ - private void generateSerializationFriendlyMethods() { - TypeConvertingMethodAdapter mv - = new TypeConvertingMethodAdapter( - cw.visitMethod(ACC_PRIVATE + ACC_FINAL, - NAME_METHOD_WRITE_REPLACE, DESCR_METHOD_WRITE_REPLACE, - null, null)); - - mv.visitCode(); - mv.visitTypeInsn(NEW, NAME_SERIALIZED_LAMBDA); - mv.visitInsn(DUP); - mv.visitLdcInsn(Type.getType(targetClass)); - mv.visitLdcInsn(factoryType.returnType().getName().replace('.', '/')); - mv.visitLdcInsn(interfaceMethodName); - mv.visitLdcInsn(interfaceMethodType.toMethodDescriptorString()); - mv.visitLdcInsn(implInfo.getReferenceKind()); - mv.visitLdcInsn(implInfo.getDeclaringClass().getName().replace('.', '/')); - mv.visitLdcInsn(implInfo.getName()); - mv.visitLdcInsn(implInfo.getMethodType().toMethodDescriptorString()); - mv.visitLdcInsn(dynamicMethodType.toMethodDescriptorString()); - mv.iconst(argDescs.length); - mv.visitTypeInsn(ANEWARRAY, JAVA_LANG_OBJECT); - for (int i = 0; i < argDescs.length; i++) { - mv.visitInsn(DUP); - mv.iconst(i); - mv.visitVarInsn(ALOAD, 0); - mv.visitFieldInsn(GETFIELD, lambdaClassName, argNames[i], argDescs[i]); - mv.boxIfTypePrimitive(Type.getType(argDescs[i])); - mv.visitInsn(AASTORE); - } - mv.visitMethodInsn(INVOKESPECIAL, NAME_SERIALIZED_LAMBDA, NAME_CTOR, - DESCR_CTOR_SERIALIZED_LAMBDA, false); - mv.visitInsn(ARETURN); - // Maxs computed by ClassWriter.COMPUTE_MAXS, these arguments ignored - mv.visitMaxs(-1, -1); - mv.visitEnd(); + private void generateSerializationFriendlyMethods(ClassBuilder clb) { + clb.withMethod(SerializationSupport.NAME_METHOD_WRITE_REPLACE, SerializationSupport.MTD_Object, ACC_PRIVATE | ACC_FINAL, + new MethodBody(new Consumer() { + @Override + public void accept(CodeBuilder cob) { + cob.new_(SerializationSupport.CD_SerializedLambda) + .dup() + .ldc(classDesc(targetClass)) + .ldc(factoryType.returnType().getName().replace('.', '/')) + .ldc(interfaceMethodName) + .ldc(interfaceMethodType.toMethodDescriptorString()) + .ldc(implInfo.getReferenceKind()) + .ldc(implInfo.getDeclaringClass().getName().replace('.', '/')) + .ldc(implInfo.getName()) + .ldc(implInfo.getMethodType().toMethodDescriptorString()) + .ldc(dynamicMethodType.toMethodDescriptorString()) + .loadConstant(argDescs.length) + .anewarray(CD_Object); + for (int i = 0; i < argDescs.length; i++) { + cob.dup() + .loadConstant(i) + .aload(0) + .getfield(lambdaClassDesc, argNames[i], argDescs[i]); + TypeConvertingMethodAdapter.boxIfTypePrimitive(cob, TypeKind.from(argDescs[i])); + cob.aastore(); + } + cob.invokespecial(SerializationSupport.CD_SerializedLambda, INIT_NAME, + SerializationSupport.MTD_CTOR_SERIALIZED_LAMBDA) + .areturn(); + } + })); } /** * Generate a readObject/writeObject method that is hostile to serialization */ - private void generateSerializationHostileMethods() { - MethodVisitor mv = cw.visitMethod(ACC_PRIVATE + ACC_FINAL, - NAME_METHOD_WRITE_OBJECT, DESCR_METHOD_WRITE_OBJECT, - null, SER_HOSTILE_EXCEPTIONS); - mv.visitCode(); - mv.visitTypeInsn(NEW, NAME_NOT_SERIALIZABLE_EXCEPTION); - mv.visitInsn(DUP); - mv.visitLdcInsn("Non-serializable lambda"); - mv.visitMethodInsn(INVOKESPECIAL, NAME_NOT_SERIALIZABLE_EXCEPTION, NAME_CTOR, - DESCR_CTOR_NOT_SERIALIZABLE_EXCEPTION, false); - mv.visitInsn(ATHROW); - mv.visitMaxs(-1, -1); - mv.visitEnd(); - - mv = cw.visitMethod(ACC_PRIVATE + ACC_FINAL, - NAME_METHOD_READ_OBJECT, DESCR_METHOD_READ_OBJECT, - null, SER_HOSTILE_EXCEPTIONS); - mv.visitCode(); - mv.visitTypeInsn(NEW, NAME_NOT_SERIALIZABLE_EXCEPTION); - mv.visitInsn(DUP); - mv.visitLdcInsn("Non-serializable lambda"); - mv.visitMethodInsn(INVOKESPECIAL, NAME_NOT_SERIALIZABLE_EXCEPTION, NAME_CTOR, - DESCR_CTOR_NOT_SERIALIZABLE_EXCEPTION, false); - mv.visitInsn(ATHROW); - mv.visitMaxs(-1, -1); - mv.visitEnd(); + private void generateSerializationHostileMethods(ClassBuilder clb) { + var hostileMethod = new Consumer() { + @Override + public void accept(MethodBuilder mb) { + ConstantPoolBuilder cp = mb.constantPool(); + ClassEntry nseCE = cp.classEntry(SerializationSupport.CD_NotSerializableException); + mb.with(ExceptionsAttribute.of(nseCE)) + .withCode(new Consumer() { + @Override + public void accept(CodeBuilder cob) { + cob.new_(nseCE) + .dup() + .ldc("Non-serializable lambda") + .invokespecial(cp.methodRefEntry(nseCE, cp.nameAndTypeEntry(INIT_NAME, + SerializationSupport.MTD_CTOR_NOT_SERIALIZABLE_EXCEPTION))) + .athrow(); + } + }); + } + }; + clb.withMethod(SerializationSupport.NAME_METHOD_WRITE_OBJECT, SerializationSupport.MTD_void_ObjectOutputStream, + ACC_PRIVATE + ACC_FINAL, hostileMethod); + clb.withMethod(SerializationSupport.NAME_METHOD_READ_OBJECT, SerializationSupport.MTD_void_ObjectInputStream, + ACC_PRIVATE + ACC_FINAL, hostileMethod); } /** - * This class generates a method body which calls the lambda implementation + * This method generates a method body which calls the lambda implementation * method, converting arguments, as needed. */ - private class ForwardingMethodGenerator extends TypeConvertingMethodAdapter { - - ForwardingMethodGenerator(MethodVisitor mv) { - super(mv); - } - - void generate(MethodType methodType) { - visitCode(); - - if (implKind == MethodHandleInfo.REF_newInvokeSpecial) { - visitTypeInsn(NEW, implMethodClassName); - visitInsn(DUP); - } - if (useImplMethodHandle) { - visitLdcInsn(implMethodCondy); - } - for (int i = 0; i < argNames.length; i++) { - visitVarInsn(ALOAD, 0); - visitFieldInsn(GETFIELD, lambdaClassName, argNames[i], argDescs[i]); - } + Consumer forwardingMethod(MethodType methodType) { + return new MethodBody(new Consumer() { + @Override + public void accept(CodeBuilder cob) { + if (implKind == MethodHandleInfo.REF_newInvokeSpecial) { + cob.new_(implMethodClassDesc) + .dup(); + } + if (useImplMethodHandle) { + ConstantPoolBuilder cp = cob.constantPool(); + cob.ldc(cp.constantDynamicEntry(cp.bsmEntry(cp.methodHandleEntry(BSM_CLASS_DATA), List.of()), + cp.nameAndTypeEntry(DEFAULT_NAME, CD_MethodHandle))); + } + for (int i = 0; i < argNames.length; i++) { + cob.aload(0) + .getfield(lambdaClassDesc, argNames[i], argDescs[i]); + } - convertArgumentTypes(methodType); + convertArgumentTypes(cob, methodType); - if (useImplMethodHandle) { - MethodType mtype = implInfo.getMethodType(); - if (implKind != MethodHandleInfo.REF_invokeStatic) { - mtype = mtype.insertParameterTypes(0, implClass); + if (useImplMethodHandle) { + MethodType mtype = implInfo.getMethodType(); + if (implKind != MethodHandleInfo.REF_invokeStatic) { + mtype = mtype.insertParameterTypes(0, implClass); + } + cob.invokevirtual(CD_MethodHandle, "invokeExact", methodDesc(mtype)); + } else { + // Invoke the method we want to forward to + cob.invoke(invocationOpcode(), implMethodClassDesc, implMethodName, implMethodDesc, implClass.isInterface()); } - visitMethodInsn(INVOKEVIRTUAL, "java/lang/invoke/MethodHandle", - "invokeExact", mtype.descriptorString(), false); - } else { - // Invoke the method we want to forward to - visitMethodInsn(invocationOpcode(), implMethodClassName, - implMethodName, implMethodDesc, - implClass.isInterface()); + // Convert the return value (if any) and return it + // Note: if adapting from non-void to void, the 'return' + // instruction will pop the unneeded result + Class implReturnClass = implMethodType.returnType(); + Class samReturnClass = methodType.returnType(); + TypeConvertingMethodAdapter.convertType(cob, implReturnClass, samReturnClass, samReturnClass); + cob.return_(TypeKind.from(samReturnClass)); } - // Convert the return value (if any) and return it - // Note: if adapting from non-void to void, the 'return' - // instruction will pop the unneeded result - Class implReturnClass = implMethodType.returnType(); - Class samReturnClass = methodType.returnType(); - convertType(implReturnClass, samReturnClass, samReturnClass); - visitInsn(getReturnOpcode(samReturnClass)); - // Maxs computed by ClassWriter.COMPUTE_MAXS,these arguments ignored - visitMaxs(-1, -1); - visitEnd(); - } - - private void convertArgumentTypes(MethodType samType) { - int lvIndex = 0; - int samParametersLength = samType.parameterCount(); - int captureArity = factoryType.parameterCount(); - for (int i = 0; i < samParametersLength; i++) { - Class argType = samType.parameterType(i); - visitVarInsn(getLoadOpcode(argType), lvIndex + 1); - lvIndex += getParameterSize(argType); - convertType(argType, implMethodType.parameterType(captureArity + i), dynamicMethodType.parameterType(i)); - } - } + }); + } - private int invocationOpcode() throws InternalError { - return switch (implKind) { - case MethodHandleInfo.REF_invokeStatic -> INVOKESTATIC; - case MethodHandleInfo.REF_newInvokeSpecial -> INVOKESPECIAL; - case MethodHandleInfo.REF_invokeVirtual -> INVOKEVIRTUAL; - case MethodHandleInfo.REF_invokeInterface -> INVOKEINTERFACE; - case MethodHandleInfo.REF_invokeSpecial -> INVOKESPECIAL; - default -> throw new InternalError("Unexpected invocation kind: " + implKind); - }; + private void convertArgumentTypes(CodeBuilder cob, MethodType samType) { + int samParametersLength = samType.parameterCount(); + int captureArity = factoryType.parameterCount(); + for (int i = 0; i < samParametersLength; i++) { + Class argType = samType.parameterType(i); + cob.loadLocal(TypeKind.from(argType), cob.parameterSlot(i)); + TypeConvertingMethodAdapter.convertType(cob, argType, implMethodType.parameterType(captureArity + i), dynamicMethodType.parameterType(i)); } } - static int getParameterSize(Class c) { - if (c == Void.TYPE) { - return 0; - } else if (c == Long.TYPE || c == Double.TYPE) { - return 2; - } - return 1; + private Opcode invocationOpcode() throws InternalError { + return switch (implKind) { + case MethodHandleInfo.REF_invokeStatic -> Opcode.INVOKESTATIC; + case MethodHandleInfo.REF_newInvokeSpecial -> Opcode.INVOKESPECIAL; + case MethodHandleInfo.REF_invokeVirtual -> Opcode.INVOKEVIRTUAL; + case MethodHandleInfo.REF_invokeInterface -> Opcode.INVOKEINTERFACE; + case MethodHandleInfo.REF_invokeSpecial -> Opcode.INVOKESPECIAL; + default -> throw new InternalError("Unexpected invocation kind: " + implKind); + }; } - static int getLoadOpcode(Class c) { - if(c == Void.TYPE) { - throw new InternalError("Unexpected void type of load opcode"); - } - return ILOAD + getOpcodeOffset(c); + static ClassDesc implClassDesc(Class cls) { + return cls.isHidden() ? null : ReferenceClassDescImpl.ofValidated(cls.descriptorString()); } - static int getReturnOpcode(Class c) { - if(c == Void.TYPE) { - return RETURN; - } - return IRETURN + getOpcodeOffset(c); + static ClassDesc classDesc(Class cls) { + return cls.isPrimitive() ? Wrapper.forPrimitiveType(cls).basicClassDescriptor() + : ReferenceClassDescImpl.ofValidated(cls.descriptorString()); } - private static int getOpcodeOffset(Class c) { - if (c.isPrimitive()) { - if (c == Long.TYPE) { - return 1; - } else if (c == Float.TYPE) { - return 2; - } else if (c == Double.TYPE) { - return 3; - } - return 0; - } else { - return 4; + static MethodTypeDesc methodDesc(MethodType mt) { + var params = new ClassDesc[mt.parameterCount()]; + for (int i = 0; i < params.length; i++) { + params[i] = classDesc(mt.parameterType(i)); } + return MethodTypeDescImpl.ofValidated(classDesc(mt.returnType()), params); } - } diff --git a/src/java.base/share/classes/java/lang/invoke/InvokerBytecodeGenerator.java b/src/java.base/share/classes/java/lang/invoke/InvokerBytecodeGenerator.java index 4e3ebb3834d..6d71296c134 100644 --- a/src/java.base/share/classes/java/lang/invoke/InvokerBytecodeGenerator.java +++ b/src/java.base/share/classes/java/lang/invoke/InvokerBytecodeGenerator.java @@ -25,30 +25,38 @@ package java.lang.invoke; -import jdk.internal.org.objectweb.asm.ClassWriter; -import jdk.internal.org.objectweb.asm.FieldVisitor; -import jdk.internal.org.objectweb.asm.Label; -import jdk.internal.org.objectweb.asm.MethodVisitor; -import jdk.internal.org.objectweb.asm.Opcodes; -import jdk.internal.org.objectweb.asm.Type; import sun.invoke.util.VerifyAccess; import sun.invoke.util.VerifyType; import sun.invoke.util.Wrapper; +import java.lang.classfile.*; +import java.lang.classfile.attribute.RuntimeVisibleAnnotationsAttribute; +import java.lang.classfile.attribute.SourceFileAttribute; +import java.lang.classfile.instruction.SwitchCase; +import java.lang.constant.ClassDesc; +import java.lang.constant.ConstantDesc; +import java.lang.constant.MethodTypeDesc; +import java.lang.invoke.LambdaForm.BasicType; +import java.lang.invoke.LambdaForm.Name; +import java.lang.invoke.LambdaForm.NamedFunction; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Set; +import java.util.function.Consumer; import java.util.stream.Stream; +import jdk.internal.constant.MethodTypeDescImpl; +import jdk.internal.constant.ReferenceClassDescImpl; -import static java.lang.invoke.LambdaForm.BasicType; -import static java.lang.invoke.LambdaForm.BasicType.*; +import static java.lang.classfile.ClassFile.*; +import static java.lang.constant.ConstantDescs.*; import static java.lang.invoke.LambdaForm.*; +import static java.lang.invoke.LambdaForm.BasicType.*; import static java.lang.invoke.MethodHandleNatives.Constants.*; import static java.lang.invoke.MethodHandleStatics.*; -import static java.lang.invoke.MethodHandles.Lookup.*; +import static java.lang.invoke.MethodHandles.Lookup.IMPL_LOOKUP; /** * Code generation backend for LambdaForm. @@ -57,32 +65,46 @@ */ class InvokerBytecodeGenerator { /** Define class names for convenience. */ - private static final String MH = "java/lang/invoke/MethodHandle"; - private static final String MHI = "java/lang/invoke/MethodHandleImpl"; - private static final String LF = "java/lang/invoke/LambdaForm"; - private static final String LFN = "java/lang/invoke/LambdaForm$Name"; - private static final String CLS = "java/lang/Class"; - private static final String OBJ = "java/lang/Object"; - private static final String OBJARY = "[Ljava/lang/Object;"; - - private static final String LOOP_CLAUSES = MHI + "$LoopClauses"; - private static final String MHARY2 = "[[L" + MH + ";"; - private static final String MH_SIG = "L" + MH + ";"; - - - private static final String LF_SIG = "L" + LF + ";"; - private static final String LFN_SIG = "L" + LFN + ";"; - private static final String LL_SIG = "(L" + OBJ + ";)L" + OBJ + ";"; - private static final String LLV_SIG = "(L" + OBJ + ";L" + OBJ + ";)V"; - private static final String CLASS_PREFIX = LF + "$"; + private static final ClassDesc CD_CasesHolder = ReferenceClassDescImpl.ofValidated("Ljava/lang/invoke/MethodHandleImpl$CasesHolder;"); + private static final ClassDesc CD_DirectMethodHandle = ReferenceClassDescImpl.ofValidated("Ljava/lang/invoke/DirectMethodHandle;"); + private static final ClassDesc CD_MethodHandleImpl = ReferenceClassDescImpl.ofValidated("Ljava/lang/invoke/MethodHandleImpl;"); + private static final ClassDesc CD_LambdaForm = ReferenceClassDescImpl.ofValidated("Ljava/lang/invoke/LambdaForm;"); + private static final ClassDesc CD_LambdaForm_Name = ReferenceClassDescImpl.ofValidated("Ljava/lang/invoke/LambdaForm$Name;"); + private static final ClassDesc CD_LoopClauses = ReferenceClassDescImpl.ofValidated("Ljava/lang/invoke/MethodHandleImpl$LoopClauses;"); + private static final ClassDesc CD_Object_array = ReferenceClassDescImpl.ofValidated("[Ljava/lang/Object;"); + private static final ClassDesc CD_MethodHandle_array = ReferenceClassDescImpl.ofValidated("[Ljava/lang/invoke/MethodHandle;"); + private static final ClassDesc CD_MethodHandle_array2 = ReferenceClassDescImpl.ofValidated("[[Ljava/lang/invoke/MethodHandle;"); + + private static final MethodTypeDesc MTD_boolean_Object = MethodTypeDescImpl.ofValidated(CD_boolean, CD_Object); + private static final MethodTypeDesc MTD_Object_int = MethodTypeDescImpl.ofValidated(CD_Object, CD_int); + private static final MethodTypeDesc MTD_Object_Class = MethodTypeDescImpl.ofValidated(CD_Object, CD_Class); + private static final MethodTypeDesc MTD_Object_Object = MethodTypeDescImpl.ofValidated(CD_Object, CD_Object); + + private static final String CLASS_PREFIX = "java/lang/invoke/LambdaForm$"; private static final String SOURCE_PREFIX = "LambdaForm$"; + // Static builders to avoid lambdas + private static final Consumer STATIC_FINAL_FIELD = new Consumer() { + @Override + public void accept(FieldBuilder fb) { + fb.withFlags(ACC_STATIC | ACC_FINAL); + } + }; + + record MethodBody(Consumer code) implements Consumer { + @Override + public void accept(MethodBuilder mb) { + mb.withCode(code); + } + }; + /** Name of its super class*/ - static final String INVOKER_SUPER_NAME = OBJ; + static final ClassDesc INVOKER_SUPER_DESC = CD_Object; /** Name of new class */ private final String name; private final String className; + private final ClassDesc classDesc; private final LambdaForm lambdaForm; private final String invokerName; @@ -92,15 +114,8 @@ class InvokerBytecodeGenerator { private int[] localsMap; // index private Class[] localClasses; // type - /** ASM bytecode generation. */ - private ClassWriter cw; - private MethodVisitor mv; private final List classData = new ArrayList<>(); - /** Single element internal class name lookup cache. */ - private Class lastClass; - private String lastInternalName; - private static final MemberName.Factory MEMBERNAME_FACTORY = MemberName.getFactory(); private static final Class HOST_CLASS = LambdaForm.class; private static final MethodHandles.Lookup LOOKUP = lookup(); @@ -126,6 +141,7 @@ private InvokerBytecodeGenerator(LambdaForm lambdaForm, int localsMapSize, } this.name = name; this.className = CLASS_PREFIX + name; + this.classDesc = ClassDesc.ofInternalName(className); this.lambdaForm = lambdaForm; this.invokerName = invokerName; this.invokerType = invokerType; @@ -188,10 +204,10 @@ private static String makeDumpableClassName(String className) { static class ClassData { final String name; - final String desc; + final ClassDesc desc; final Object value; - ClassData(String name, String desc, Object value) { + ClassData(String name, ClassDesc desc, Object value) { this.name = name; this.desc = desc; this.value = value; @@ -204,15 +220,15 @@ public String toString() { } String classData(Object arg) { - String desc; + ClassDesc desc; if (arg instanceof Class) { - desc = "Ljava/lang/Class;"; + desc = CD_Class; } else if (arg instanceof MethodHandle) { - desc = MH_SIG; + desc = CD_MethodHandle; } else if (arg instanceof LambdaForm) { - desc = LF_SIG; + desc = CD_LambdaForm; } else { - desc = "Ljava/lang/Object;"; + desc = CD_Object; } // unique static variable name @@ -231,16 +247,6 @@ String classData(Object arg) { return name; } - private static String debugString(Object arg) { - if (arg instanceof MethodHandle mh) { - MemberName member = mh.internalMemberName(); - if (member != null) - return member.toString(); - return mh.debugString(); - } - return arg.toString(); - } - /** * Extract the MemberName of a newly-defined method. */ @@ -265,27 +271,25 @@ private static MemberName resolveInvokerMember(Class invokerClass, String nam /** * Set up class file generation. */ - private ClassWriter classFilePrologue() { - final int NOT_ACC_PUBLIC = 0; // not ACC_PUBLIC - ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS + ClassWriter.COMPUTE_FRAMES); - setClassWriter(cw); - cw.visit(CLASSFILE_VERSION, NOT_ACC_PUBLIC + Opcodes.ACC_FINAL + Opcodes.ACC_SUPER, - className, null, INVOKER_SUPER_NAME, null); - cw.visitSource(SOURCE_PREFIX + name, null); - return cw; - } - - private void methodPrologue() { - String invokerDesc = invokerType.toMethodDescriptorString(); - mv = cw.visitMethod(Opcodes.ACC_STATIC, invokerName, invokerDesc, null, null); + private byte[] classFileSetup(Consumer config) { + try { + return ClassFile.of().build(classDesc, new Consumer<>() { + @Override + public void accept(ClassBuilder clb) { + clb.withFlags(ACC_FINAL | ACC_SUPER) + .withSuperclass(INVOKER_SUPER_DESC) + .with(SourceFileAttribute.of(clb.constantPool().utf8Entry(SOURCE_PREFIX + name))); + config.accept(clb); + } + }); + } catch (RuntimeException e) { + throw new BytecodeGenerationException(e); + } } - /** - * Tear down class file generation. - */ - private void methodEpilogue() { - mv.visitMaxs(0, 0); - mv.visitEnd(); + private void methodSetup(ClassBuilder clb, Consumer config) { + var invokerDesc = methodDesc(invokerType); + clb.withMethod(invokerName, invokerDesc, ACC_STATIC, config); } /** @@ -318,202 +322,49 @@ private Object classDataValues() { * to initialize the static final fields with the live class data * LambdaForms can't use condy due to bootstrapping issue. */ - static void clinit(ClassWriter cw, String className, List classData) { + static void clinit(ClassBuilder clb, ClassDesc classDesc, List classData) { if (classData.isEmpty()) return; for (ClassData p : classData) { // add the static field - FieldVisitor fv = cw.visitField(Opcodes.ACC_STATIC|Opcodes.ACC_FINAL, p.name, p.desc, null, null); - fv.visitEnd(); - } - - MethodVisitor mv = cw.visitMethod(Opcodes.ACC_STATIC, "", "()V", null, null); - mv.visitCode(); - mv.visitLdcInsn(Type.getType("L" + className + ";")); - mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/invoke/MethodHandles", - "classData", "(Ljava/lang/Class;)Ljava/lang/Object;", false); - if (classData.size() == 1) { - ClassData p = classData.get(0); - mv.visitTypeInsn(Opcodes.CHECKCAST, p.desc.substring(1, p.desc.length()-1)); - mv.visitFieldInsn(Opcodes.PUTSTATIC, className, p.name, p.desc); - } else { - mv.visitTypeInsn(Opcodes.CHECKCAST, "java/util/List"); - mv.visitVarInsn(Opcodes.ASTORE, 0); - int index = 0; - for (ClassData p : classData) { - // initialize the static field - mv.visitVarInsn(Opcodes.ALOAD, 0); - emitIconstInsn(mv, index++); - mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, "java/util/List", - "get", "(I)Ljava/lang/Object;", true); - mv.visitTypeInsn(Opcodes.CHECKCAST, p.desc.substring(1, p.desc.length()-1)); - mv.visitFieldInsn(Opcodes.PUTSTATIC, className, p.name, p.desc); - } - } - mv.visitInsn(Opcodes.RETURN); - mv.visitMaxs(2, 1); - mv.visitEnd(); - } - - /* - * Low-level emit helpers. - */ - private void emitConst(Object con) { - if (con == null) { - mv.visitInsn(Opcodes.ACONST_NULL); - return; - } - if (con instanceof Integer) { - emitIconstInsn((int) con); - return; - } - if (con instanceof Byte) { - emitIconstInsn((byte)con); - return; - } - if (con instanceof Short) { - emitIconstInsn((short)con); - return; - } - if (con instanceof Character) { - emitIconstInsn((char)con); - return; - } - if (con instanceof Long) { - long x = (long) con; - short sx = (short)x; - if (x == sx) { - if (sx >= 0 && sx <= 1) { - mv.visitInsn(Opcodes.LCONST_0 + (int) sx); - } else { - emitIconstInsn((int) x); - mv.visitInsn(Opcodes.I2L); - } - return; - } - } - if (con instanceof Float) { - float x = (float) con; - short sx = (short)x; - if (x == sx) { - if (sx >= 0 && sx <= 2) { - mv.visitInsn(Opcodes.FCONST_0 + (int) sx); + clb.withField(p.name, p.desc, STATIC_FINAL_FIELD); + } + + clb.withMethod(CLASS_INIT_NAME, MTD_void, ACC_STATIC, new MethodBody(new Consumer() { + @Override + public void accept(CodeBuilder cob) { + cob.loadConstant(classDesc) + .invokestatic(CD_MethodHandles, "classData", MTD_Object_Class); + if (classData.size() == 1) { + ClassData p = classData.get(0); + cob.checkcast(p.desc) + .putstatic(classDesc, p.name, p.desc); } else { - emitIconstInsn((int) x); - mv.visitInsn(Opcodes.I2F); - } - return; - } - } - if (con instanceof Double) { - double x = (double) con; - short sx = (short)x; - if (x == sx) { - if (sx >= 0 && sx <= 1) { - mv.visitInsn(Opcodes.DCONST_0 + (int) sx); - } else { - emitIconstInsn((int) x); - mv.visitInsn(Opcodes.I2D); + cob.checkcast(CD_List) + .astore(0); + int index = 0; + var listGet = cob.constantPool().interfaceMethodRefEntry(CD_List, "get", MTD_Object_int); + for (ClassData p : classData) { + // initialize the static field + cob.aload(0) + .loadConstant(index++) + .invokeinterface(listGet) + .checkcast(p.desc) + .putstatic(classDesc, p.name, p.desc); + } } - return; + cob.return_(); } - } - if (con instanceof Boolean) { - emitIconstInsn((boolean) con ? 1 : 0); - return; - } - // fall through: - mv.visitLdcInsn(con); - } - - private void emitIconstInsn(final int cst) { - emitIconstInsn(mv, cst); - } - - private static void emitIconstInsn(MethodVisitor mv, int cst) { - if (cst >= -1 && cst <= 5) { - mv.visitInsn(Opcodes.ICONST_0 + cst); - } else if (cst >= Byte.MIN_VALUE && cst <= Byte.MAX_VALUE) { - mv.visitIntInsn(Opcodes.BIPUSH, cst); - } else if (cst >= Short.MIN_VALUE && cst <= Short.MAX_VALUE) { - mv.visitIntInsn(Opcodes.SIPUSH, cst); - } else { - mv.visitLdcInsn(cst); - } - } - - /* - * NOTE: These load/store methods use the localsMap to find the correct index! - */ - private void emitLoadInsn(BasicType type, int index) { - int opcode = loadInsnOpcode(type); - mv.visitVarInsn(opcode, localsMap[index]); + })); } - private int loadInsnOpcode(BasicType type) throws InternalError { - return switch (type) { - case I_TYPE -> Opcodes.ILOAD; - case J_TYPE -> Opcodes.LLOAD; - case F_TYPE -> Opcodes.FLOAD; - case D_TYPE -> Opcodes.DLOAD; - case L_TYPE -> Opcodes.ALOAD; - default -> throw new InternalError("unknown type: " + type); - }; - } - private void emitAloadInsn(int index) { - emitLoadInsn(L_TYPE, index); + private void emitLoadInsn(CodeBuilder cob, TypeKind type, int index) { + cob.loadLocal(type, localsMap[index]); } - private void emitStoreInsn(BasicType type, int index) { - int opcode = storeInsnOpcode(type); - mv.visitVarInsn(opcode, localsMap[index]); - } - - private int storeInsnOpcode(BasicType type) throws InternalError { - return switch (type) { - case I_TYPE -> Opcodes.ISTORE; - case J_TYPE -> Opcodes.LSTORE; - case F_TYPE -> Opcodes.FSTORE; - case D_TYPE -> Opcodes.DSTORE; - case L_TYPE -> Opcodes.ASTORE; - default -> throw new InternalError("unknown type: " + type); - }; - } - private void emitAstoreInsn(int index) { - emitStoreInsn(L_TYPE, index); - } - - private byte arrayTypeCode(Wrapper elementType) { - return (byte) switch (elementType) { - case BOOLEAN -> Opcodes.T_BOOLEAN; - case BYTE -> Opcodes.T_BYTE; - case CHAR -> Opcodes.T_CHAR; - case SHORT -> Opcodes.T_SHORT; - case INT -> Opcodes.T_INT; - case LONG -> Opcodes.T_LONG; - case FLOAT -> Opcodes.T_FLOAT; - case DOUBLE -> Opcodes.T_DOUBLE; - case OBJECT -> 0; // in place of Opcodes.T_OBJECT - default -> throw new InternalError(); - }; - } - - private int arrayInsnOpcode(byte tcode, int aaop) throws InternalError { - assert(aaop == Opcodes.AASTORE || aaop == Opcodes.AALOAD); - int xas = switch (tcode) { - case Opcodes.T_BOOLEAN -> Opcodes.BASTORE; - case Opcodes.T_BYTE -> Opcodes.BASTORE; - case Opcodes.T_CHAR -> Opcodes.CASTORE; - case Opcodes.T_SHORT -> Opcodes.SASTORE; - case Opcodes.T_INT -> Opcodes.IASTORE; - case Opcodes.T_LONG -> Opcodes.LASTORE; - case Opcodes.T_FLOAT -> Opcodes.FASTORE; - case Opcodes.T_DOUBLE -> Opcodes.DASTORE; - case 0 -> Opcodes.AASTORE; - default -> throw new InternalError(); - }; - return xas - Opcodes.AASTORE + aaop; + private void emitStoreInsn(CodeBuilder cob, TypeKind type, int index) { + cob.storeLocal(type, localsMap[index]); } /** @@ -521,11 +372,8 @@ private int arrayInsnOpcode(byte tcode, int aaop) throws InternalError { * * @param wrapper primitive type class to box. */ - private void emitBoxing(Wrapper wrapper) { - String owner = "java/lang/" + wrapper.wrapperType().getSimpleName(); - String name = "valueOf"; - String desc = "(" + wrapper.basicTypeChar() + ")L" + owner + ";"; - mv.visitMethodInsn(Opcodes.INVOKESTATIC, owner, name, desc, false); + private void emitBoxing(CodeBuilder cob, TypeKind tk) { + TypeConvertingMethodAdapter.box(cob, tk); } /** @@ -533,12 +381,15 @@ private void emitBoxing(Wrapper wrapper) { * * @param wrapper wrapper type class to unbox. */ - private void emitUnboxing(Wrapper wrapper) { - String owner = "java/lang/" + wrapper.wrapperType().getSimpleName(); - String name = wrapper.primitiveSimpleName() + "Value"; - String desc = "()" + wrapper.basicTypeChar(); - emitReferenceCast(wrapper.wrapperType(), null); - mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, owner, name, desc, false); + private void emitUnboxing(CodeBuilder cob, TypeKind target) { + switch (target) { + case BooleanType -> emitReferenceCast(cob, Boolean.class, null); + case CharType -> emitReferenceCast(cob, Character.class, null); + case ByteType, DoubleType, FloatType, IntType, LongType, ShortType -> + emitReferenceCast(cob, Number.class, null); + default -> {} + } + TypeConvertingMethodAdapter.unbox(cob, target); } /** @@ -549,7 +400,7 @@ private void emitUnboxing(Wrapper wrapper) { * @param pclass type of value required on stack * @param arg compile-time representation of value on stack (Node, constant) or null if none */ - private void emitImplicitConversion(BasicType ptype, Class pclass, Object arg) { + private void emitImplicitConversion(CodeBuilder cob, BasicType ptype, Class pclass, Object arg) { assert(basicType(pclass) == ptype); // boxing/unboxing handled by caller if (pclass == ptype.basicTypeClass() && ptype != L_TYPE) return; // nothing to do @@ -557,14 +408,14 @@ private void emitImplicitConversion(BasicType ptype, Class pclass, Object arg case L_TYPE: if (VerifyType.isNullConversion(Object.class, pclass, false)) { if (PROFILE_LEVEL > 0) - emitReferenceCast(Object.class, arg); + emitReferenceCast(cob, Object.class, arg); return; } - emitReferenceCast(pclass, arg); + emitReferenceCast(cob, pclass, arg); return; case I_TYPE: if (!VerifyType.isNullConversion(int.class, pclass, false)) - emitPrimCast(ptype.basicTypeWrapper(), Wrapper.forPrimitiveType(pclass)); + emitPrimCast(cob, ptype.basicTypeKind(), TypeKind.from(pclass)); return; } throw newInternalError("bad implicit conversion: tc="+ptype+": "+pclass); @@ -582,7 +433,7 @@ private boolean assertStaticType(Class cls, Name n) { return false; } - private void emitReferenceCast(Class cls, Object arg) { + private void emitReferenceCast(CodeBuilder cob, Class cls, Object arg) { Name writeBack = null; // local to write back result if (arg instanceof Name n) { if (lambdaForm.useCount(n) > 1) { @@ -594,53 +445,23 @@ private void emitReferenceCast(Class cls, Object arg) { } } if (isStaticallyNameable(cls)) { - String sig = getInternalName(cls); - mv.visitTypeInsn(Opcodes.CHECKCAST, sig); + ClassDesc sig = classDesc(cls); + cob.checkcast(sig); } else { - mv.visitFieldInsn(Opcodes.GETSTATIC, className, classData(cls), "Ljava/lang/Class;"); - mv.visitInsn(Opcodes.SWAP); - mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, CLS, "cast", LL_SIG, false); + cob.getstatic(classDesc, classData(cls), CD_Class) + .swap() + .invokevirtual(CD_Class, "cast", MTD_Object_Object); if (Object[].class.isAssignableFrom(cls)) - mv.visitTypeInsn(Opcodes.CHECKCAST, OBJARY); + cob.checkcast(CD_Object_array); else if (PROFILE_LEVEL > 0) - mv.visitTypeInsn(Opcodes.CHECKCAST, OBJ); + cob.checkcast(CD_Object); } if (writeBack != null) { - mv.visitInsn(Opcodes.DUP); - emitAstoreInsn(writeBack.index()); + cob.dup(); + emitStoreInsn(cob, TypeKind.ReferenceType, writeBack.index()); } } - /** - * Emits an actual return instruction conforming to the given return type. - */ - private void emitReturnInsn(BasicType type) { - int opcode = switch (type) { - case I_TYPE -> Opcodes.IRETURN; - case J_TYPE -> Opcodes.LRETURN; - case F_TYPE -> Opcodes.FRETURN; - case D_TYPE -> Opcodes.DRETURN; - case L_TYPE -> Opcodes.ARETURN; - case V_TYPE -> Opcodes.RETURN; - default -> throw new InternalError("unknown return type: " + type); - }; - mv.visitInsn(opcode); - } - - private String getInternalName(Class c) { - if (c == Object.class) return OBJ; - else if (c == Object[].class) return OBJARY; - else if (c == Class.class) return CLS; - else if (c == MethodHandle.class) return MH; - assert(VerifyAccess.ensureTypeVisible(c, Object.class)) : c.getName(); - - if (c == lastClass) { - return lastInternalName; - } - lastClass = c; - return lastInternalName = c.getName().replace('.', '/'); - } - private static MemberName resolveFrom(String name, MethodType type, Class holder) { assert(!UNSAFE.shouldBeInitialized(holder)) : holder + "not initialized"; MemberName member = new MemberName(holder, name, type, REF_invokeStatic); @@ -713,173 +534,151 @@ static MemberName generateCustomizedCode(LambdaForm form, MethodType invokerType } /** Generates code to check that actual receiver and LambdaForm matches */ - private boolean checkActualReceiver() { + private boolean checkActualReceiver(CodeBuilder cob) { // Expects MethodHandle on the stack and actual receiver MethodHandle in slot #0 - mv.visitInsn(Opcodes.DUP); - mv.visitVarInsn(Opcodes.ALOAD, localsMap[0]); - mv.visitMethodInsn(Opcodes.INVOKESTATIC, MHI, "assertSame", LLV_SIG, false); + cob.dup() + .aload(0) + .invokestatic(CD_MethodHandleImpl, "assertSame", MethodTypeDescImpl.ofValidated(CD_void, CD_Object, CD_Object)); return true; } - static String className(String cn) { - assert checkClassName(cn): "Class not found: " + cn; - return cn; - } - - static boolean checkClassName(String cn) { - Type tp = Type.getType(cn); - // additional sanity so only valid "L;" descriptors work - if (tp.getSort() != Type.OBJECT) { - return false; - } - try { - Class c = Class.forName(tp.getClassName(), false, null); - return true; - } catch (ClassNotFoundException e) { - return false; - } - } - - static final String DONTINLINE_SIG = className("Ljdk/internal/vm/annotation/DontInline;"); - static final String FORCEINLINE_SIG = className("Ljdk/internal/vm/annotation/ForceInline;"); - static final String HIDDEN_SIG = className("Ljdk/internal/vm/annotation/Hidden;"); - static final String INJECTEDPROFILE_SIG = className("Ljava/lang/invoke/InjectedProfile;"); - static final String LF_COMPILED_SIG = className("Ljava/lang/invoke/LambdaForm$Compiled;"); + static final Annotation DONTINLINE = Annotation.of(ReferenceClassDescImpl.ofValidated("Ljdk/internal/vm/annotation/DontInline;")); + static final Annotation FORCEINLINE = Annotation.of(ReferenceClassDescImpl.ofValidated("Ljdk/internal/vm/annotation/ForceInline;")); + static final Annotation HIDDEN = Annotation.of(ReferenceClassDescImpl.ofValidated("Ljdk/internal/vm/annotation/Hidden;")); + static final Annotation INJECTEDPROFILE = Annotation.of(ReferenceClassDescImpl.ofValidated("Ljava/lang/invoke/InjectedProfile;")); + static final Annotation LF_COMPILED = Annotation.of(ReferenceClassDescImpl.ofValidated("Ljava/lang/invoke/LambdaForm$Compiled;")); /** * Generate an invoker method for the passed {@link LambdaForm}. */ private byte[] generateCustomizedCodeBytes() { - classFilePrologue(); - addMethod(); - clinit(cw, className, classData); - bogusMethod(lambdaForm); - - return toByteArray(); + final byte[] classFile = classFileSetup(new Consumer() { + @Override + public void accept(ClassBuilder clb) { + addMethod(clb); + clinit(clb, classDesc, classData); + bogusMethod(clb, lambdaForm); + } + }); + return classFile; } - void setClassWriter(ClassWriter cw) { - this.cw = cw; - } + void addMethod(ClassBuilder clb) { + methodSetup(clb, new Consumer() { + @Override + public void accept(MethodBuilder mb) { - void addMethod() { - methodPrologue(); + List annotations = new ArrayList<>(3); - // Suppress this method in backtraces displayed to the user. - mv.visitAnnotation(HIDDEN_SIG, true); + // Suppress this method in backtraces displayed to the user. + annotations.add(HIDDEN); - // Mark this method as a compiled LambdaForm - mv.visitAnnotation(LF_COMPILED_SIG, true); + // Mark this method as a compiled LambdaForm + annotations.add(LF_COMPILED); - if (lambdaForm.forceInline) { - // Force inlining of this invoker method. - mv.visitAnnotation(FORCEINLINE_SIG, true); - } else { - mv.visitAnnotation(DONTINLINE_SIG, true); - } + if (lambdaForm.forceInline) { + // Force inlining of this invoker method. + annotations.add(FORCEINLINE); + } else { + annotations.add(DONTINLINE); + } + mb.accept(RuntimeVisibleAnnotationsAttribute.of(annotations)); + + classData(lambdaForm); // keep LambdaForm instance & its compiled form lifetime tightly coupled. + + mb.withCode(new Consumer() { + @Override + public void accept(CodeBuilder cob) { + if (lambdaForm.customized != null) { + // Since LambdaForm is customized for a particular MethodHandle, it's safe to substitute + // receiver MethodHandle (at slot #0) with an embedded constant and use it instead. + // It enables more efficient code generation in some situations, since embedded constants + // are compile-time constants for JIT compiler. + cob.getstatic(classDesc, classData(lambdaForm.customized), CD_MethodHandle) + .checkcast(CD_MethodHandle); + assert(checkActualReceiver(cob)); // expects MethodHandle on top of the stack + cob.astore(0); + } - classData(lambdaForm); // keep LambdaForm instance & its compiled form lifetime tightly coupled. - - if (lambdaForm.customized != null) { - // Since LambdaForm is customized for a particular MethodHandle, it's safe to substitute - // receiver MethodHandle (at slot #0) with an embedded constant and use it instead. - // It enables more efficient code generation in some situations, since embedded constants - // are compile-time constants for JIT compiler. - mv.visitFieldInsn(Opcodes.GETSTATIC, className, classData(lambdaForm.customized), MH_SIG); - mv.visitTypeInsn(Opcodes.CHECKCAST, MH); - assert(checkActualReceiver()); // expects MethodHandle on top of the stack - mv.visitVarInsn(Opcodes.ASTORE, localsMap[0]); - } + // iterate over the form's names, generating bytecode instructions for each + // start iterating at the first name following the arguments + Name onStack = null; + for (int i = lambdaForm.arity; i < lambdaForm.names.length; i++) { + Name name = lambdaForm.names[i]; + + emitStoreResult(cob, onStack); + onStack = name; // unless otherwise modified below + MethodHandleImpl.Intrinsic intr = name.function.intrinsicName(); + switch (intr) { + case SELECT_ALTERNATIVE: + assert lambdaForm.isSelectAlternative(i); + if (PROFILE_GWT) { + assert(name.arguments[0] instanceof Name n && + n.refersTo(MethodHandleImpl.class, "profileBoolean")); + mb.with(RuntimeVisibleAnnotationsAttribute.of(List.of(INJECTEDPROFILE))); + } + onStack = emitSelectAlternative(cob, name, lambdaForm.names[i+1]); + i++; // skip MH.invokeBasic of the selectAlternative result + continue; + case GUARD_WITH_CATCH: + assert lambdaForm.isGuardWithCatch(i); + onStack = emitGuardWithCatch(cob, i); + i += 2; // jump to the end of GWC idiom + continue; + case TRY_FINALLY: + assert lambdaForm.isTryFinally(i); + onStack = emitTryFinally(cob, i); + i += 2; // jump to the end of the TF idiom + continue; + case TABLE_SWITCH: + assert lambdaForm.isTableSwitch(i); + int numCases = (Integer) name.function.intrinsicData(); + onStack = emitTableSwitch(cob, i, numCases); + i += 2; // jump to the end of the TS idiom + continue; + case LOOP: + assert lambdaForm.isLoop(i); + onStack = emitLoop(cob, i); + i += 2; // jump to the end of the LOOP idiom + continue; + case ARRAY_LOAD: + emitArrayLoad(cob, name); + continue; + case ARRAY_STORE: + emitArrayStore(cob, name); + continue; + case ARRAY_LENGTH: + emitArrayLength(cob, name); + continue; + case IDENTITY: + assert(name.arguments.length == 1); + emitPushArguments(cob, name, 0); + continue; + case ZERO: + assert(name.arguments.length == 0); + cob.loadConstant((ConstantDesc)name.type.basicTypeWrapper().zero()); + continue; + case NONE: + // no intrinsic associated + break; + default: + throw newInternalError("Unknown intrinsic: "+intr); + } + + MemberName member = name.function.member(); + if (isStaticallyInvocable(member)) { + emitStaticInvoke(cob, member, name); + } else { + emitInvoke(cob, name); + } + } - // iterate over the form's names, generating bytecode instructions for each - // start iterating at the first name following the arguments - Name onStack = null; - for (int i = lambdaForm.arity; i < lambdaForm.names.length; i++) { - Name name = lambdaForm.names[i]; - - emitStoreResult(onStack); - onStack = name; // unless otherwise modified below - MethodHandleImpl.Intrinsic intr = name.function.intrinsicName(); - switch (intr) { - case SELECT_ALTERNATIVE: - assert lambdaForm.isSelectAlternative(i); - if (PROFILE_GWT) { - assert(name.arguments[0] instanceof Name n && - n.refersTo(MethodHandleImpl.class, "profileBoolean")); - mv.visitAnnotation(INJECTEDPROFILE_SIG, true); + // return statement + emitReturn(cob, onStack); } - onStack = emitSelectAlternative(name, lambdaForm.names[i+1]); - i++; // skip MH.invokeBasic of the selectAlternative result - continue; - case GUARD_WITH_CATCH: - assert lambdaForm.isGuardWithCatch(i); - onStack = emitGuardWithCatch(i); - i += 2; // jump to the end of GWC idiom - continue; - case TRY_FINALLY: - assert lambdaForm.isTryFinally(i); - onStack = emitTryFinally(i); - i += 2; // jump to the end of the TF idiom - continue; - case TABLE_SWITCH: - assert lambdaForm.isTableSwitch(i); - int numCases = (Integer) name.function.intrinsicData(); - onStack = emitTableSwitch(i, numCases); - i += 2; // jump to the end of the TS idiom - continue; - case LOOP: - assert lambdaForm.isLoop(i); - onStack = emitLoop(i); - i += 2; // jump to the end of the LOOP idiom - continue; - case ARRAY_LOAD: - emitArrayLoad(name); - continue; - case ARRAY_STORE: - emitArrayStore(name); - continue; - case ARRAY_LENGTH: - emitArrayLength(name); - continue; - case IDENTITY: - assert(name.arguments.length == 1); - emitPushArguments(name, 0); - continue; - case ZERO: - assert(name.arguments.length == 0); - emitConst(name.type.basicTypeWrapper().zero()); - continue; - case NONE: - // no intrinsic associated - break; - default: - throw newInternalError("Unknown intrinsic: "+intr); - } - - MemberName member = name.function.member(); - if (isStaticallyInvocable(member)) { - emitStaticInvoke(member, name); - } else { - emitInvoke(name); + }); } - } - - // return statement - emitReturn(onStack); - - methodEpilogue(); - } - - /* - * @throws BytecodeGenerationException if something goes wrong when - * generating the byte code - */ - private byte[] toByteArray() { - try { - return cw.toByteArray(); - } catch (RuntimeException e) { - throw new BytecodeGenerationException(e); - } + }); } /** @@ -892,48 +691,60 @@ static final class BytecodeGenerationException extends RuntimeException { } } - void emitArrayLoad(Name name) { emitArrayOp(name, Opcodes.AALOAD); } - void emitArrayStore(Name name) { emitArrayOp(name, Opcodes.AASTORE); } - void emitArrayLength(Name name) { emitArrayOp(name, Opcodes.ARRAYLENGTH); } + void emitArrayLoad(CodeBuilder cob, Name name) { + Class elementType = name.function.methodType().parameterType(0).getComponentType(); + assert elementType != null; + emitPushArguments(cob, name, 0); + if (elementType.isPrimitive()) { + cob.arrayLoad(TypeKind.from(elementType)); + } else { + cob.aaload(); + } + } - void emitArrayOp(Name name, int arrayOpcode) { - assert arrayOpcode == Opcodes.AALOAD || arrayOpcode == Opcodes.AASTORE || arrayOpcode == Opcodes.ARRAYLENGTH; + void emitArrayStore(CodeBuilder cob, Name name) { Class elementType = name.function.methodType().parameterType(0).getComponentType(); assert elementType != null; - emitPushArguments(name, 0); - if (arrayOpcode != Opcodes.ARRAYLENGTH && elementType.isPrimitive()) { - Wrapper w = Wrapper.forPrimitiveType(elementType); - arrayOpcode = arrayInsnOpcode(arrayTypeCode(w), arrayOpcode); + emitPushArguments(cob, name, 0); + if (elementType.isPrimitive()) { + cob.arrayStore(TypeKind.from(elementType)); + } else { + cob.aastore(); } - mv.visitInsn(arrayOpcode); + } + + void emitArrayLength(CodeBuilder cob, Name name) { + assert name.function.methodType().parameterType(0).isArray(); + emitPushArguments(cob, name, 0); + cob.arraylength(); } /** * Emit an invoke for the given name. */ - void emitInvoke(Name name) { + void emitInvoke(CodeBuilder cob, Name name) { assert(!name.isLinkerMethodInvoke()); // should use the static path for these if (true) { // push receiver MethodHandle target = name.function.resolvedHandle(); assert(target != null) : name.exprString(); - mv.visitFieldInsn(Opcodes.GETSTATIC, className, classData(target), MH_SIG); - emitReferenceCast(MethodHandle.class, target); + cob.getstatic(classDesc, classData(target), CD_MethodHandle); + emitReferenceCast(cob, MethodHandle.class, target); } else { // load receiver - emitAloadInsn(0); - emitReferenceCast(MethodHandle.class, null); - mv.visitFieldInsn(Opcodes.GETFIELD, MH, "form", LF_SIG); - mv.visitFieldInsn(Opcodes.GETFIELD, LF, "names", LFN_SIG); + cob.aload(0); + emitReferenceCast(cob, MethodHandle.class, null); + cob.getfield(CD_MethodHandle, "form", CD_LambdaForm) + .getfield(CD_LambdaForm, "names", CD_LambdaForm_Name); // TODO more to come } // push arguments - emitPushArguments(name, 0); + emitPushArguments(cob, name, 0); // invocation MethodType type = name.function.methodType(); - mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, MH, "invokeBasic", type.basicType().toMethodDescriptorString(), false); + cob.invokevirtual(CD_MethodHandle, "invokeBasic", methodDesc(type.basicType())); } private static final Class[] STATICALLY_INVOCABLE_PACKAGES = { @@ -1020,19 +831,18 @@ static boolean isStaticallyNameable(Class cls) { return false; } - void emitStaticInvoke(Name name) { - emitStaticInvoke(name.function.member(), name); + void emitStaticInvoke(CodeBuilder cob, Name name) { + emitStaticInvoke(cob, name.function.member(), name); } /** * Emit an invoke for the given name, using the MemberName directly. */ - void emitStaticInvoke(MemberName member, Name name) { + void emitStaticInvoke(CodeBuilder cob, MemberName member, Name name) { assert(member.equals(name.function.member())); Class defc = member.getDeclaringClass(); - String cname = getInternalName(defc); + ClassDesc cdesc = classDesc(defc); String mname = member.getName(); - String mtype; byte refKind = member.getReferenceKind(); if (refKind == REF_invokeSpecial) { // in order to pass the verifier, we need to convert this to invokevirtual in all cases @@ -1043,16 +853,16 @@ void emitStaticInvoke(MemberName member, Name name) { assert(!(member.getDeclaringClass().isInterface() && refKind == REF_invokeVirtual)); // push arguments - emitPushArguments(name, 0); + emitPushArguments(cob, name, 0); // invocation if (member.isMethod()) { - mtype = member.getMethodType().toMethodDescriptorString(); - mv.visitMethodInsn(refKindOpcode(refKind), cname, mname, mtype, - member.getDeclaringClass().isInterface()); + var methodTypeDesc = methodDesc(member.getMethodType()); + cob.invoke(refKindOpcode(refKind), cdesc, mname, methodTypeDesc, + member.getDeclaringClass().isInterface()); } else { - mtype = MethodType.toFieldDescriptorString(member.getFieldType()); - mv.visitFieldInsn(refKindOpcode(refKind), cname, mname, mtype); + var fieldTypeDesc = classDesc(member.getFieldType()); + cob.fieldAccess(refKindOpcode(refKind), cdesc, mname, fieldTypeDesc); } // Issue a type assertion for the result, so we can avoid casts later. if (name.type == L_TYPE) { @@ -1064,16 +874,16 @@ void emitStaticInvoke(MemberName member, Name name) { } } - int refKindOpcode(byte refKind) { + Opcode refKindOpcode(byte refKind) { switch (refKind) { - case REF_invokeVirtual: return Opcodes.INVOKEVIRTUAL; - case REF_invokeStatic: return Opcodes.INVOKESTATIC; - case REF_invokeSpecial: return Opcodes.INVOKESPECIAL; - case REF_invokeInterface: return Opcodes.INVOKEINTERFACE; - case REF_getField: return Opcodes.GETFIELD; - case REF_putField: return Opcodes.PUTFIELD; - case REF_getStatic: return Opcodes.GETSTATIC; - case REF_putStatic: return Opcodes.PUTSTATIC; + case REF_invokeVirtual: return Opcode.INVOKEVIRTUAL; + case REF_invokeStatic: return Opcode.INVOKESTATIC; + case REF_invokeSpecial: return Opcode.INVOKESPECIAL; + case REF_invokeInterface: return Opcode.INVOKEINTERFACE; + case REF_getField: return Opcode.GETFIELD; + case REF_putField: return Opcode.PUTFIELD; + case REF_getStatic: return Opcode.GETSTATIC; + case REF_putStatic: return Opcode.PUTSTATIC; } throw new InternalError("refKind="+refKind); } @@ -1089,40 +899,40 @@ int refKindOpcode(byte refKind) { * t4:I=MethodHandle.invokeBasic(t3:L,a1:I);t4:I} * } */ - private Name emitSelectAlternative(Name selectAlternativeName, Name invokeBasicName) { + private Name emitSelectAlternative(CodeBuilder cob, Name selectAlternativeName, Name invokeBasicName) { assert isStaticallyInvocable(invokeBasicName); Name receiver = (Name) invokeBasicName.arguments[0]; - Label L_fallback = new Label(); - Label L_done = new Label(); + Label L_fallback = cob.newLabel(); + Label L_done = cob.newLabel(); // load test result - emitPushArgument(selectAlternativeName, 0); + emitPushArgument(cob, selectAlternativeName, 0); // if_icmpne L_fallback - mv.visitJumpInsn(Opcodes.IFEQ, L_fallback); + cob.ifeq(L_fallback); // invoke selectAlternativeName.arguments[1] Class[] preForkClasses = localClasses.clone(); - emitPushArgument(selectAlternativeName, 1); // get 2nd argument of selectAlternative - emitAstoreInsn(receiver.index()); // store the MH in the receiver slot - emitStaticInvoke(invokeBasicName); + emitPushArgument(cob, selectAlternativeName, 1); // get 2nd argument of selectAlternative + emitStoreInsn(cob, TypeKind.ReferenceType, receiver.index()); // store the MH in the receiver slot + emitStaticInvoke(cob, invokeBasicName); // goto L_done - mv.visitJumpInsn(Opcodes.GOTO, L_done); + cob.goto_w(L_done); // L_fallback: - mv.visitLabel(L_fallback); + cob.labelBinding(L_fallback); // invoke selectAlternativeName.arguments[2] System.arraycopy(preForkClasses, 0, localClasses, 0, preForkClasses.length); - emitPushArgument(selectAlternativeName, 2); // get 3rd argument of selectAlternative - emitAstoreInsn(receiver.index()); // store the MH in the receiver slot - emitStaticInvoke(invokeBasicName); + emitPushArgument(cob, selectAlternativeName, 2); // get 3rd argument of selectAlternative + emitStoreInsn(cob, TypeKind.ReferenceType, receiver.index()); // store the MH in the receiver slot + emitStaticInvoke(cob, invokeBasicName); // L_done: - mv.visitLabel(L_done); + cob.labelBinding(L_done); // for now do not bother to merge typestate; just reset to the dominator state System.arraycopy(preForkClasses, 0, localClasses, 0, preForkClasses.length); @@ -1149,57 +959,57 @@ private Name emitSelectAlternative(Name selectAlternativeName, Name invokeBasicN * return a3.invokeBasic(ex, a6, a7); * }} */ - private Name emitGuardWithCatch(int pos) { + private Name emitGuardWithCatch(CodeBuilder cob, int pos) { Name args = lambdaForm.names[pos]; Name invoker = lambdaForm.names[pos+1]; Name result = lambdaForm.names[pos+2]; - Label L_startBlock = new Label(); - Label L_endBlock = new Label(); - Label L_handler = new Label(); - Label L_done = new Label(); + Label L_startBlock = cob.newLabel(); + Label L_endBlock = cob.newLabel(); + Label L_handler = cob.newLabel(); + Label L_done = cob.newLabel(); Class returnType = result.function.resolvedHandle().type().returnType(); MethodType type = args.function.resolvedHandle().type() .dropParameterTypes(0,1) .changeReturnType(returnType); - mv.visitTryCatchBlock(L_startBlock, L_endBlock, L_handler, "java/lang/Throwable"); + cob.exceptionCatch(L_startBlock, L_endBlock, L_handler, CD_Throwable); // Normal case - mv.visitLabel(L_startBlock); + cob.labelBinding(L_startBlock); // load target - emitPushArgument(invoker, 0); - emitPushArguments(args, 1); // skip 1st argument: method handle - mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, MH, "invokeBasic", type.basicType().toMethodDescriptorString(), false); - mv.visitLabel(L_endBlock); - mv.visitJumpInsn(Opcodes.GOTO, L_done); + emitPushArgument(cob, invoker, 0); + emitPushArguments(cob, args, 1); // skip 1st argument: method handle + cob.invokevirtual(CD_MethodHandle, "invokeBasic", methodDesc(type.basicType())); + cob.labelBinding(L_endBlock); + cob.goto_w(L_done); // Exceptional case - mv.visitLabel(L_handler); + cob.labelBinding(L_handler); // Check exception's type - mv.visitInsn(Opcodes.DUP); + cob.dup(); // load exception class - emitPushArgument(invoker, 1); - mv.visitInsn(Opcodes.SWAP); - mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Class", "isInstance", "(Ljava/lang/Object;)Z", false); - Label L_rethrow = new Label(); - mv.visitJumpInsn(Opcodes.IFEQ, L_rethrow); + emitPushArgument(cob, invoker, 1); + cob.swap(); + cob.invokevirtual(CD_Class, "isInstance", MTD_boolean_Object); + Label L_rethrow = cob.newLabel(); + cob.ifeq(L_rethrow); // Invoke catcher // load catcher - emitPushArgument(invoker, 2); - mv.visitInsn(Opcodes.SWAP); - emitPushArguments(args, 1); // skip 1st argument: method handle + emitPushArgument(cob, invoker, 2); + cob.swap(); + emitPushArguments(cob, args, 1); // skip 1st argument: method handle MethodType catcherType = type.insertParameterTypes(0, Throwable.class); - mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, MH, "invokeBasic", catcherType.basicType().toMethodDescriptorString(), false); - mv.visitJumpInsn(Opcodes.GOTO, L_done); + cob.invokevirtual(CD_MethodHandle, "invokeBasic", methodDesc(catcherType.basicType())); + cob.goto_w(L_done); - mv.visitLabel(L_rethrow); - mv.visitInsn(Opcodes.ATHROW); + cob.labelBinding(L_rethrow); + cob.athrow(); - mv.visitLabel(L_done); + cob.labelBinding(L_done); return result; } @@ -1264,15 +1074,15 @@ private Name emitGuardWithCatch(int pos) { * } * * = depends on whether the return type takes up 2 stack slots. */ - private Name emitTryFinally(int pos) { + private Name emitTryFinally(CodeBuilder cob, int pos) { Name args = lambdaForm.names[pos]; Name invoker = lambdaForm.names[pos+1]; Name result = lambdaForm.names[pos+2]; - Label lFrom = new Label(); - Label lTo = new Label(); - Label lCatch = new Label(); - Label lDone = new Label(); + Label lFrom = cob.newLabel(); + Label lTo = cob.newLabel(); + Label lCatch = cob.newLabel(); + Label lDone = cob.newLabel(); Class returnType = result.function.resolvedHandle().type().returnType(); BasicType basicReturnType = BasicType.basicType(returnType); @@ -1285,68 +1095,64 @@ private Name emitTryFinally(int pos) { if (isNonVoid) { cleanupType = cleanupType.insertParameterTypes(1, returnType); } - String cleanupDesc = cleanupType.basicType().toMethodDescriptorString(); + MethodTypeDesc cleanupDesc = methodDesc(cleanupType.basicType()); // exception handler table - mv.visitTryCatchBlock(lFrom, lTo, lCatch, "java/lang/Throwable"); + cob.exceptionCatch(lFrom, lTo, lCatch, CD_Throwable); // TRY: - mv.visitLabel(lFrom); - emitPushArgument(invoker, 0); // load target - emitPushArguments(args, 1); // load args (skip 0: method handle) - mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, MH, "invokeBasic", type.basicType().toMethodDescriptorString(), false); - mv.visitLabel(lTo); + cob.labelBinding(lFrom); + emitPushArgument(cob, invoker, 0); // load target + emitPushArguments(cob, args, 1); // load args (skip 0: method handle) + cob.invokevirtual(CD_MethodHandle, "invokeBasic", methodDesc(type.basicType())); + cob.labelBinding(lTo); // FINALLY_NORMAL: int index = extendLocalsMap(new Class[]{ returnType }); if (isNonVoid) { - emitStoreInsn(basicReturnType, index); + emitStoreInsn(cob, basicReturnType.basicTypeKind(), index); } - emitPushArgument(invoker, 1); // load cleanup - mv.visitInsn(Opcodes.ACONST_NULL); + emitPushArgument(cob, invoker, 1); // load cleanup + cob.loadConstant(null); if (isNonVoid) { - emitLoadInsn(basicReturnType, index); + emitLoadInsn(cob, basicReturnType.basicTypeKind(), index); } - emitPushArguments(args, 1); // load args (skip 0: method handle) - mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, MH, "invokeBasic", cleanupDesc, false); - mv.visitJumpInsn(Opcodes.GOTO, lDone); + emitPushArguments(cob, args, 1); // load args (skip 0: method handle) + cob.invokevirtual(CD_MethodHandle, "invokeBasic", cleanupDesc); + cob.goto_w(lDone); // CATCH: - mv.visitLabel(lCatch); - mv.visitInsn(Opcodes.DUP); + cob.labelBinding(lCatch); + cob.dup(); // FINALLY_EXCEPTIONAL: - emitPushArgument(invoker, 1); // load cleanup - mv.visitInsn(Opcodes.SWAP); + emitPushArgument(cob, invoker, 1); // load cleanup + cob.swap(); if (isNonVoid) { - emitZero(BasicType.basicType(returnType)); // load default for result + emitZero(cob, BasicType.basicType(returnType)); // load default for result } - emitPushArguments(args, 1); // load args (skip 0: method handle) - mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, MH, "invokeBasic", cleanupDesc, false); + emitPushArguments(cob, args, 1); // load args (skip 0: method handle) + cob.invokevirtual(CD_MethodHandle, "invokeBasic", cleanupDesc); if (isNonVoid) { - emitPopInsn(basicReturnType); + emitPopInsn(cob, basicReturnType); } - mv.visitInsn(Opcodes.ATHROW); + cob.athrow(); // DONE: - mv.visitLabel(lDone); + cob.labelBinding(lDone); return result; } - private void emitPopInsn(BasicType type) { - mv.visitInsn(popInsnOpcode(type)); - } - - private static int popInsnOpcode(BasicType type) { - return switch (type) { - case I_TYPE, F_TYPE, L_TYPE -> Opcodes.POP; - case J_TYPE, D_TYPE -> Opcodes.POP2; + private void emitPopInsn(CodeBuilder cob, BasicType type) { + switch (type) { + case I_TYPE, F_TYPE, L_TYPE -> cob.pop(); + case J_TYPE, D_TYPE -> cob.pop2(); default -> throw new InternalError("unknown type: " + type); - }; + } } - private Name emitTableSwitch(int pos, int numCases) { + private Name emitTableSwitch(CodeBuilder cob, int pos, int numCases) { Name args = lambdaForm.names[pos]; Name invoker = lambdaForm.names[pos + 1]; Name result = lambdaForm.names[pos + 2]; @@ -1355,45 +1161,44 @@ private Name emitTableSwitch(int pos, int numCases) { MethodType caseType = args.function.resolvedHandle().type() .dropParameterTypes(0, 1) // drop collector .changeReturnType(returnType); - String caseDescriptor = caseType.basicType().toMethodDescriptorString(); + MethodTypeDesc caseDescriptor = methodDesc(caseType.basicType()); - emitPushArgument(invoker, 2); // push cases - mv.visitFieldInsn(Opcodes.GETFIELD, "java/lang/invoke/MethodHandleImpl$CasesHolder", "cases", - "[Ljava/lang/invoke/MethodHandle;"); + emitPushArgument(cob, invoker, 2); // push cases + cob.getfield(CD_CasesHolder, "cases", CD_MethodHandle_array); int casesLocal = extendLocalsMap(new Class[] { MethodHandle[].class }); - emitStoreInsn(L_TYPE, casesLocal); + emitStoreInsn(cob, TypeKind.ReferenceType, casesLocal); - Label endLabel = new Label(); - Label defaultLabel = new Label(); - Label[] caseLabels = new Label[numCases]; - for (int i = 0; i < caseLabels.length; i++) { - caseLabels[i] = new Label(); + Label endLabel = cob.newLabel(); + Label defaultLabel = cob.newLabel(); + List cases = new ArrayList<>(numCases); + for (int i = 0; i < numCases; i++) { + cases.add(SwitchCase.of(i, cob.newLabel())); } - emitPushArgument(invoker, 0); // push switch input - mv.visitTableSwitchInsn(0, numCases - 1, defaultLabel, caseLabels); + emitPushArgument(cob, invoker, 0); // push switch input + cob.tableswitch(0, numCases - 1, defaultLabel, cases); - mv.visitLabel(defaultLabel); - emitPushArgument(invoker, 1); // push default handle - emitPushArguments(args, 1); // again, skip collector - mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, MH, "invokeBasic", caseDescriptor, false); - mv.visitJumpInsn(Opcodes.GOTO, endLabel); + cob.labelBinding(defaultLabel); + emitPushArgument(cob, invoker, 1); // push default handle + emitPushArguments(cob, args, 1); // again, skip collector + cob.invokevirtual(CD_MethodHandle, "invokeBasic", caseDescriptor); + cob.goto_(endLabel); for (int i = 0; i < numCases; i++) { - mv.visitLabel(caseLabels[i]); + cob.labelBinding(cases.get(i).target()); // Load the particular case: - emitLoadInsn(L_TYPE, casesLocal); - emitIconstInsn(i); - mv.visitInsn(Opcodes.AALOAD); + emitLoadInsn(cob, TypeKind.ReferenceType, casesLocal); + cob.loadConstant(i); + cob.aaload(); // invoke it: - emitPushArguments(args, 1); // again, skip collector - mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, MH, "invokeBasic", caseDescriptor, false); + emitPushArguments(cob, args, 1); // again, skip collector + cob.invokevirtual(CD_MethodHandle, "invokeBasic", caseDescriptor); - mv.visitJumpInsn(Opcodes.GOTO, endLabel); + cob.goto_(endLabel); } - mv.visitLabel(endLabel); + cob.labelBinding(endLabel); return result; } @@ -1480,7 +1285,7 @@ private Name emitTableSwitch(int pos, int numCases) { * GOTO DONE // jump beyond end of clauses to return from loop * } */ - private Name emitLoop(int pos) { + private Name emitLoop(CodeBuilder cob, int pos) { Name args = lambdaForm.names[pos]; Name invoker = lambdaForm.names[pos+1]; Name result = lambdaForm.names[pos+2]; @@ -1488,8 +1293,9 @@ private Name emitLoop(int pos) { // extract clause and loop-local state types // find the type info in the loop invocation BasicType[] loopClauseTypes = (BasicType[]) invoker.arguments[0]; - Class[] loopLocalStateTypes = Stream.of(loopClauseTypes). - filter(bt -> bt != BasicType.V_TYPE).map(BasicType::basicTypeClass).toArray(Class[]::new); + Class[] loopLocalStateTypes = Stream.of(loopClauseTypes) + .filter(bt -> bt != BasicType.V_TYPE) + .map(BasicType::basicTypeClass).toArray(Class[]::new); Class[] localTypes = new Class[loopLocalStateTypes.length + 1]; localTypes[0] = MethodHandleImpl.LoopClauses.class; System.arraycopy(loopLocalStateTypes, 0, localTypes, 1, loopLocalStateTypes.length); @@ -1513,61 +1319,61 @@ private Name emitLoop(int pos) { final int preds = 3; final int finis = 4; - Label lLoop = new Label(); - Label lDone = new Label(); + Label lLoop = cob.newLabel(); + Label lDone = cob.newLabel(); Label lNext; // PREINIT: - emitPushArgument(MethodHandleImpl.LoopClauses.class, invoker.arguments[1]); - mv.visitFieldInsn(Opcodes.GETFIELD, LOOP_CLAUSES, "clauses", MHARY2); - emitAstoreInsn(clauseDataIndex); + emitPushArgument(cob, MethodHandleImpl.LoopClauses.class, invoker.arguments[1]); + cob.getfield(CD_LoopClauses, "clauses", CD_MethodHandle_array2); + emitStoreInsn(cob, TypeKind.ReferenceType, clauseDataIndex); // INIT: for (int c = 0, state = 0; c < nClauses; ++c) { MethodType cInitType = loopType.changeReturnType(loopClauseTypes[c].basicTypeClass()); - emitLoopHandleInvoke(invoker, inits, c, args, false, cInitType, loopLocalStateTypes, clauseDataIndex, + emitLoopHandleInvoke(cob, invoker, inits, c, args, false, cInitType, loopLocalStateTypes, clauseDataIndex, firstLoopStateIndex); if (cInitType.returnType() != void.class) { - emitStoreInsn(BasicType.basicType(cInitType.returnType()), firstLoopStateIndex + state); + emitStoreInsn(cob, BasicType.basicType(cInitType.returnType()).basicTypeKind(), firstLoopStateIndex + state); ++state; } } // LOOP: - mv.visitLabel(lLoop); + cob.labelBinding(lLoop); for (int c = 0, state = 0; c < nClauses; ++c) { - lNext = new Label(); + lNext = cob.newLabel(); MethodType stepType = loopHandleType.changeReturnType(loopClauseTypes[c].basicTypeClass()); boolean isVoid = stepType.returnType() == void.class; // invoke loop step - emitLoopHandleInvoke(invoker, steps, c, args, true, stepType, loopLocalStateTypes, clauseDataIndex, + emitLoopHandleInvoke(cob, invoker, steps, c, args, true, stepType, loopLocalStateTypes, clauseDataIndex, firstLoopStateIndex); if (!isVoid) { - emitStoreInsn(BasicType.basicType(stepType.returnType()), firstLoopStateIndex + state); + emitStoreInsn(cob, BasicType.basicType(stepType.returnType()).basicTypeKind(), firstLoopStateIndex + state); ++state; } // invoke loop predicate - emitLoopHandleInvoke(invoker, preds, c, args, true, predType, loopLocalStateTypes, clauseDataIndex, + emitLoopHandleInvoke(cob, invoker, preds, c, args, true, predType, loopLocalStateTypes, clauseDataIndex, firstLoopStateIndex); - mv.visitJumpInsn(Opcodes.IFNE, lNext); + cob.ifne(lNext); // invoke fini - emitLoopHandleInvoke(invoker, finis, c, args, true, finiType, loopLocalStateTypes, clauseDataIndex, + emitLoopHandleInvoke(cob, invoker, finis, c, args, true, finiType, loopLocalStateTypes, clauseDataIndex, firstLoopStateIndex); - mv.visitJumpInsn(Opcodes.GOTO, lDone); + cob.goto_w(lDone); // this is the beginning of the next loop clause - mv.visitLabel(lNext); + cob.labelBinding(lNext); } - mv.visitJumpInsn(Opcodes.GOTO, lLoop); + cob.goto_w(lLoop); // DONE: - mv.visitLabel(lDone); + cob.labelBinding(lDone); return result; } @@ -1588,69 +1394,67 @@ private int extendLocalsMap(Class[] types) { return firstSlot; } - private void emitLoopHandleInvoke(Name holder, int handles, int clause, Name args, boolean pushLocalState, + private void emitLoopHandleInvoke(CodeBuilder cob, Name holder, int handles, int clause, Name args, boolean pushLocalState, MethodType type, Class[] loopLocalStateTypes, int clauseDataSlot, int firstLoopStateSlot) { // load handle for clause - emitPushClauseArray(clauseDataSlot, handles); - emitIconstInsn(clause); - mv.visitInsn(Opcodes.AALOAD); + emitPushClauseArray(cob, clauseDataSlot, handles); + cob.loadConstant(clause); + cob.aaload(); // load loop state (preceding the other arguments) if (pushLocalState) { for (int s = 0; s < loopLocalStateTypes.length; ++s) { - emitLoadInsn(BasicType.basicType(loopLocalStateTypes[s]), firstLoopStateSlot + s); + emitLoadInsn(cob, BasicType.basicType(loopLocalStateTypes[s]).basicTypeKind(), firstLoopStateSlot + s); } } // load loop args (skip 0: method handle) - emitPushArguments(args, 1); - mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, MH, "invokeBasic", type.toMethodDescriptorString(), false); + emitPushArguments(cob, args, 1); + cob.invokevirtual(CD_MethodHandle, "invokeBasic", methodDesc(type)); } - private void emitPushClauseArray(int clauseDataSlot, int which) { - emitAloadInsn(clauseDataSlot); - emitIconstInsn(which - 1); - mv.visitInsn(Opcodes.AALOAD); + private void emitPushClauseArray(CodeBuilder cob, int clauseDataSlot, int which) { + emitLoadInsn(cob, TypeKind.ReferenceType, clauseDataSlot); + cob.loadConstant(which - 1); + cob.aaload(); } - private void emitZero(BasicType type) { - mv.visitInsn(switch (type) { - case I_TYPE -> Opcodes.ICONST_0; - case J_TYPE -> Opcodes.LCONST_0; - case F_TYPE -> Opcodes.FCONST_0; - case D_TYPE -> Opcodes.DCONST_0; - case L_TYPE -> Opcodes.ACONST_NULL; + private void emitZero(CodeBuilder cob, BasicType type) { + switch (type) { + case I_TYPE -> cob.iconst_0(); + case J_TYPE -> cob.lconst_0(); + case F_TYPE -> cob.fconst_0(); + case D_TYPE -> cob.dconst_0(); + case L_TYPE -> cob.aconst_null(); default -> throw new InternalError("unknown type: " + type); - }); + }; } - private void emitPushArguments(Name args, int start) { + private void emitPushArguments(CodeBuilder cob, Name args, int start) { MethodType type = args.function.methodType(); for (int i = start; i < args.arguments.length; i++) { - emitPushArgument(type.parameterType(i), args.arguments[i]); + emitPushArgument(cob, type.parameterType(i), args.arguments[i]); } } - private void emitPushArgument(Name name, int paramIndex) { + private void emitPushArgument(CodeBuilder cob, Name name, int paramIndex) { Object arg = name.arguments[paramIndex]; Class ptype = name.function.methodType().parameterType(paramIndex); - emitPushArgument(ptype, arg); + emitPushArgument(cob, ptype, arg); } - private void emitPushArgument(Class ptype, Object arg) { + private void emitPushArgument(CodeBuilder cob, Class ptype, Object arg) { BasicType bptype = basicType(ptype); if (arg instanceof Name n) { - emitLoadInsn(n.type, n.index()); - emitImplicitConversion(n.type, ptype, n); - } else if (arg == null && bptype == L_TYPE) { - mv.visitInsn(Opcodes.ACONST_NULL); - } else if (arg instanceof String && bptype == L_TYPE) { - mv.visitLdcInsn(arg); + emitLoadInsn(cob, n.type.basicTypeKind(), n.index()); + emitImplicitConversion(cob, n.type, ptype, n); + } else if ((arg == null || arg instanceof String) && bptype == L_TYPE) { + cob.loadConstant((ConstantDesc)arg); } else { if (Wrapper.isWrapperType(arg.getClass()) && bptype != L_TYPE) { - emitConst(arg); + cob.loadConstant((ConstantDesc)arg); } else { - mv.visitFieldInsn(Opcodes.GETSTATIC, className, classData(arg), "Ljava/lang/Object;"); - emitImplicitConversion(L_TYPE, ptype, arg); + cob.getstatic(classDesc, classData(arg), CD_Object); + emitImplicitConversion(cob, L_TYPE, ptype, arg); } } } @@ -1658,44 +1462,44 @@ private void emitPushArgument(Class ptype, Object arg) { /** * Store the name to its local, if necessary. */ - private void emitStoreResult(Name name) { + private void emitStoreResult(CodeBuilder cob, Name name) { if (name != null && name.type != V_TYPE) { // non-void: actually assign - emitStoreInsn(name.type, name.index()); + emitStoreInsn(cob, name.type.basicTypeKind(), name.index()); } } /** * Emits a return statement from a LF invoker. If required, the result type is cast to the correct return type. */ - private void emitReturn(Name onStack) { + private void emitReturn(CodeBuilder cob, Name onStack) { // return statement Class rclass = invokerType.returnType(); BasicType rtype = lambdaForm.returnType(); assert(rtype == basicType(rclass)); // must agree if (rtype == V_TYPE) { // void - mv.visitInsn(Opcodes.RETURN); + cob.return_(); // it doesn't matter what rclass is; the JVM will discard any value } else { LambdaForm.Name rn = lambdaForm.names[lambdaForm.result]; // put return value on the stack if it is not already there if (rn != onStack) { - emitLoadInsn(rtype, lambdaForm.result); + emitLoadInsn(cob, rtype.basicTypeKind(), lambdaForm.result); } - emitImplicitConversion(rtype, rclass, rn); + emitImplicitConversion(cob, rtype, rclass, rn); // generate actual return statement - emitReturnInsn(rtype); + cob.return_(rtype.basicTypeKind()); } } /** * Emit a type conversion bytecode casting from "from" to "to". */ - private void emitPrimCast(Wrapper from, Wrapper to) { + private void emitPrimCast(CodeBuilder cob, TypeKind from, TypeKind to) { // Here's how. // - indicates forbidden // <-> indicates implicit @@ -1708,80 +1512,10 @@ private void emitPrimCast(Wrapper from, Wrapper to) { // long - l2i,i2b l2i,i2s l2i,i2c l2i <-> l2f l2d // float - f2i,i2b f2i,i2s f2i,i2c f2i f2l <-> f2d // double - d2i,i2b d2i,i2s d2i,i2c d2i d2l d2f <-> - if (from == to) { - // no cast required, should be dead code anyway - return; - } - if (from.isSubwordOrInt()) { - // cast from {byte,short,char,int} to anything - emitI2X(to); - } else { - // cast from {long,float,double} to anything - if (to.isSubwordOrInt()) { - // cast to {byte,short,char,int} - emitX2I(from); - if (to.bitWidth() < 32) { - // targets other than int require another conversion - emitI2X(to); - } - } else { - // cast to {long,float,double} - this is verbose - boolean error = false; - switch (from) { - case LONG -> { - switch (to) { - case FLOAT -> mv.visitInsn(Opcodes.L2F); - case DOUBLE -> mv.visitInsn(Opcodes.L2D); - default -> error = true; - } - } - case FLOAT -> { - switch (to) { - case LONG -> mv.visitInsn(Opcodes.F2L); - case DOUBLE -> mv.visitInsn(Opcodes.F2D); - default -> error = true; - } - } - case DOUBLE -> { - switch (to) { - case LONG -> mv.visitInsn(Opcodes.D2L); - case FLOAT -> mv.visitInsn(Opcodes.D2F); - default -> error = true; - } - } - default -> error = true; - } - if (error) { - throw new IllegalStateException("unhandled prim cast: " + from + "2" + to); - } - } - } - } - - private void emitI2X(Wrapper type) { - switch (type) { - case BYTE: mv.visitInsn(Opcodes.I2B); break; - case SHORT: mv.visitInsn(Opcodes.I2S); break; - case CHAR: mv.visitInsn(Opcodes.I2C); break; - case INT: /* naught */ break; - case LONG: mv.visitInsn(Opcodes.I2L); break; - case FLOAT: mv.visitInsn(Opcodes.I2F); break; - case DOUBLE: mv.visitInsn(Opcodes.I2D); break; - case BOOLEAN: - // For compatibility with ValueConversions and explicitCastArguments: - mv.visitInsn(Opcodes.ICONST_1); - mv.visitInsn(Opcodes.IAND); - break; - default: throw new InternalError("unknown type: " + type); - } - } - - private void emitX2I(Wrapper type) { - switch (type) { - case LONG -> mv.visitInsn(Opcodes.L2I); - case FLOAT -> mv.visitInsn(Opcodes.F2I); - case DOUBLE -> mv.visitInsn(Opcodes.D2I); - default -> throw new InternalError("unknown type: " + type); + if (from != to && from != TypeKind.BooleanType) try { + cob.conversion(from, to); + } catch (IllegalArgumentException e) { + throw new IllegalStateException("unhandled prim cast: " + from + "2" + to); } } @@ -1798,51 +1532,61 @@ static MemberName generateLambdaFormInterpreterEntryPoint(MethodType mt) { } private byte[] generateLambdaFormInterpreterEntryPointBytes() { - classFilePrologue(); - methodPrologue(); - - // Suppress this method in backtraces displayed to the user. - mv.visitAnnotation(HIDDEN_SIG, true); - - // Don't inline the interpreter entry. - mv.visitAnnotation(DONTINLINE_SIG, true); - - // create parameter array - emitIconstInsn(invokerType.parameterCount()); - mv.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/Object"); - - // fill parameter array - for (int i = 0; i < invokerType.parameterCount(); i++) { - Class ptype = invokerType.parameterType(i); - mv.visitInsn(Opcodes.DUP); - emitIconstInsn(i); - emitLoadInsn(basicType(ptype), i); - // box if primitive type - if (ptype.isPrimitive()) { - emitBoxing(Wrapper.forPrimitiveType(ptype)); + final byte[] classFile = classFileSetup(new Consumer() { + @Override + public void accept(ClassBuilder clb) { + methodSetup(clb, new Consumer() { + @Override + public void accept(MethodBuilder mb) { + + mb.with(RuntimeVisibleAnnotationsAttribute.of(List.of( + HIDDEN, // Suppress this method in backtraces displayed to the user. + DONTINLINE // Don't inline the interpreter entry. + ))); + + mb.withCode(new Consumer() { + @Override + public void accept(CodeBuilder cob) { + // create parameter array + cob.loadConstant(invokerType.parameterCount()); + cob.anewarray(CD_Object); + + // fill parameter array + for (int i = 0; i < invokerType.parameterCount(); i++) { + Class ptype = invokerType.parameterType(i); + cob.dup(); + cob.loadConstant(i); + emitLoadInsn(cob, basicType(ptype).basicTypeKind(), i); + // box if primitive type + if (ptype.isPrimitive()) { + emitBoxing(cob, TypeKind.from(ptype)); + } + cob.aastore(); + } + // invoke + cob.aload(0); + cob.getfield(CD_MethodHandle, "form", CD_LambdaForm); + cob.swap(); // swap form and array; avoid local variable + cob.invokevirtual(CD_LambdaForm, "interpretWithArguments", MethodTypeDescImpl.ofValidated(CD_Object, CD_Object_array)); + + // maybe unbox + Class rtype = invokerType.returnType(); + TypeKind rtypeK = TypeKind.from(rtype); + if (rtype.isPrimitive() && rtype != void.class) { + emitUnboxing(cob, rtypeK); + } + + // return statement + cob.return_(rtypeK); + } + }); + } + }); + clinit(clb, classDesc, classData); + bogusMethod(clb, invokerType); } - mv.visitInsn(Opcodes.AASTORE); - } - // invoke - emitAloadInsn(0); - mv.visitFieldInsn(Opcodes.GETFIELD, MH, "form", "Ljava/lang/invoke/LambdaForm;"); - mv.visitInsn(Opcodes.SWAP); // swap form and array; avoid local variable - mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, LF, "interpretWithArguments", "([Ljava/lang/Object;)Ljava/lang/Object;", false); - - // maybe unbox - Class rtype = invokerType.returnType(); - if (rtype.isPrimitive() && rtype != void.class) { - emitUnboxing(Wrapper.forPrimitiveType(rtype)); - } - - // return statement - emitReturnInsn(basicType(rtype)); - - methodEpilogue(); - clinit(cw, className, classData); - bogusMethod(invokerType); - - return cw.toByteArray(); + }); + return classFile; } /** @@ -1857,73 +1601,101 @@ static MemberName generateNamedFunctionInvoker(MethodTypeForm typeForm) { private byte[] generateNamedFunctionInvokerImpl(MethodTypeForm typeForm) { MethodType dstType = typeForm.erasedType(); - classFilePrologue(); - methodPrologue(); - - // Suppress this method in backtraces displayed to the user. - mv.visitAnnotation(HIDDEN_SIG, true); - - // Force inlining of this invoker method. - mv.visitAnnotation(FORCEINLINE_SIG, true); - - // Load receiver - emitAloadInsn(0); - - // Load arguments from array - for (int i = 0; i < dstType.parameterCount(); i++) { - emitAloadInsn(1); - emitIconstInsn(i); - mv.visitInsn(Opcodes.AALOAD); - - // Maybe unbox - Class dptype = dstType.parameterType(i); - if (dptype.isPrimitive()) { - Wrapper dstWrapper = Wrapper.forBasicType(dptype); - Wrapper srcWrapper = dstWrapper.isSubwordOrInt() ? Wrapper.INT : dstWrapper; // narrow subword from int - emitUnboxing(srcWrapper); - emitPrimCast(srcWrapper, dstWrapper); + final byte[] classFile = classFileSetup(new Consumer() { + @Override + public void accept(ClassBuilder clb) { + methodSetup(clb, new Consumer() { + @Override + public void accept(MethodBuilder mb) { + + mb.with(RuntimeVisibleAnnotationsAttribute.of(List.of( + HIDDEN, // Suppress this method in backtraces displayed to the user. + FORCEINLINE // Force inlining of this invoker method. + ))); + + mb.withCode(new Consumer() { + @Override + public void accept(CodeBuilder cob) { + // Load receiver + cob.aload(0); + + // Load arguments from array + for (int i = 0; i < dstType.parameterCount(); i++) { + cob.aload(1); + cob.loadConstant(i); + cob.aaload(); + + // Maybe unbox + Class dptype = dstType.parameterType(i); + if (dptype.isPrimitive()) { + TypeKind dstTK = TypeKind.from(dptype); + TypeKind srcTK = dstTK.asLoadable(); + emitUnboxing(cob, srcTK); + emitPrimCast(cob, srcTK, dstTK); + } + } + + // Invoke + MethodTypeDesc targetDesc = methodDesc(dstType.basicType()); + cob.invokevirtual(CD_MethodHandle, "invokeBasic", targetDesc); + + // Box primitive types + Class rtype = dstType.returnType(); + if (rtype != void.class && rtype.isPrimitive()) { + TypeKind srcTK = TypeKind.from(rtype); + TypeKind dstTK = srcTK.asLoadable(); + // boolean casts not allowed + emitPrimCast(cob, srcTK, dstTK); + emitBoxing(cob, dstTK); + } + + // If the return type is void we return a null reference. + if (rtype == void.class) { + cob.aconst_null(); + } + cob.areturn(); // NOTE: NamedFunction invokers always return a reference value. + } + }); + } + }); + clinit(clb, classDesc, classData); + bogusMethod(clb, dstType); } - } - - // Invoke - String targetDesc = dstType.basicType().toMethodDescriptorString(); - mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, MH, "invokeBasic", targetDesc, false); - - // Box primitive types - Class rtype = dstType.returnType(); - if (rtype != void.class && rtype.isPrimitive()) { - Wrapper srcWrapper = Wrapper.forBasicType(rtype); - Wrapper dstWrapper = srcWrapper.isSubwordOrInt() ? Wrapper.INT : srcWrapper; // widen subword to int - // boolean casts not allowed - emitPrimCast(srcWrapper, dstWrapper); - emitBoxing(dstWrapper); - } - - // If the return type is void we return a null reference. - if (rtype == void.class) { - mv.visitInsn(Opcodes.ACONST_NULL); - } - emitReturnInsn(L_TYPE); // NOTE: NamedFunction invokers always return a reference value. - - methodEpilogue(); - clinit(cw, className, classData); - bogusMethod(dstType); - - return cw.toByteArray(); + }); + return classFile; } /** * Emit a bogus method that just loads some string constants. This is to get the constants into the constant pool * for debugging purposes. */ - private void bogusMethod(Object os) { + private void bogusMethod(ClassBuilder clb, Object os) { if (dumper().isEnabled()) { - mv = cw.visitMethod(Opcodes.ACC_STATIC, "dummy", "()V", null, null); - mv.visitLdcInsn(os.toString()); - mv.visitInsn(Opcodes.POP); - mv.visitInsn(Opcodes.RETURN); - mv.visitMaxs(0, 0); - mv.visitEnd(); + clb.withMethod("dummy", MTD_void, ACC_STATIC, new MethodBody(new Consumer() { + @Override + public void accept(CodeBuilder cob) { + cob.loadConstant(os.toString()); + cob.pop(); + cob.return_(); + } + })); + } + } + + static ClassDesc classDesc(Class cls) { +// assert(VerifyAccess.isTypeVisible(cls, Object.class)) : cls.getName(); + return cls.isPrimitive() ? Wrapper.forPrimitiveType(cls).basicClassDescriptor() + : cls == MethodHandle.class ? CD_MethodHandle + : cls == DirectMethodHandle.class ? CD_DirectMethodHandle + : cls == Object.class ? CD_Object + : ReferenceClassDescImpl.ofValidated(cls.descriptorString()); + } + + static MethodTypeDesc methodDesc(MethodType mt) { + var params = new ClassDesc[mt.parameterCount()]; + for (int i = 0; i < params.length; i++) { + params[i] = classDesc(mt.parameterType(i)); } + return MethodTypeDescImpl.ofValidated(classDesc(mt.returnType()), params); } } diff --git a/src/java.base/share/classes/java/lang/invoke/LambdaForm.java b/src/java.base/share/classes/java/lang/invoke/LambdaForm.java index 823f527b6c4..09ea5df4a23 100644 --- a/src/java.base/share/classes/java/lang/invoke/LambdaForm.java +++ b/src/java.base/share/classes/java/lang/invoke/LambdaForm.java @@ -25,6 +25,7 @@ package java.lang.invoke; +import java.lang.classfile.TypeKind; import jdk.internal.perf.PerfCounter; import jdk.internal.vm.annotation.DontInline; import jdk.internal.vm.annotation.Hidden; @@ -137,12 +138,12 @@ class LambdaForm { public static final int VOID_RESULT = -1, LAST_RESULT = -2; enum BasicType { - L_TYPE('L', Object.class, Wrapper.OBJECT), // all reference types - I_TYPE('I', int.class, Wrapper.INT), - J_TYPE('J', long.class, Wrapper.LONG), - F_TYPE('F', float.class, Wrapper.FLOAT), - D_TYPE('D', double.class, Wrapper.DOUBLE), // all primitive types - V_TYPE('V', void.class, Wrapper.VOID); // not valid in all contexts + L_TYPE('L', Object.class, Wrapper.OBJECT, TypeKind.ReferenceType), // all reference types + I_TYPE('I', int.class, Wrapper.INT, TypeKind.IntType), + J_TYPE('J', long.class, Wrapper.LONG, TypeKind.LongType), + F_TYPE('F', float.class, Wrapper.FLOAT, TypeKind.FloatType), + D_TYPE('D', double.class, Wrapper.DOUBLE, TypeKind.DoubleType), // all primitive types + V_TYPE('V', void.class, Wrapper.VOID, TypeKind.VoidType); // not valid in all contexts static final @Stable BasicType[] ALL_TYPES = BasicType.values(); static final @Stable BasicType[] ARG_TYPES = Arrays.copyOf(ALL_TYPES, ALL_TYPES.length-1); @@ -153,11 +154,13 @@ enum BasicType { final char btChar; final Class btClass; final Wrapper btWrapper; + final TypeKind btKind; - private BasicType(char btChar, Class btClass, Wrapper wrapper) { + private BasicType(char btChar, Class btClass, Wrapper wrapper, TypeKind typeKind) { this.btChar = btChar; this.btClass = btClass; this.btWrapper = wrapper; + this.btKind = typeKind; } char basicTypeChar() { @@ -169,6 +172,9 @@ Class basicTypeClass() { Wrapper basicTypeWrapper() { return btWrapper; } + TypeKind basicTypeKind() { + return btKind; + } int basicTypeSlots() { return btWrapper.stackSlots(); } diff --git a/src/java.base/share/classes/java/lang/invoke/MethodHandleImpl.java b/src/java.base/share/classes/java/lang/invoke/MethodHandleImpl.java index e79c8463d30..57446c9b2fd 100644 --- a/src/java.base/share/classes/java/lang/invoke/MethodHandleImpl.java +++ b/src/java.base/share/classes/java/lang/invoke/MethodHandleImpl.java @@ -27,8 +27,9 @@ import jdk.internal.access.JavaLangInvokeAccess; import jdk.internal.access.SharedSecrets; +import jdk.internal.constant.MethodTypeDescImpl; +import jdk.internal.constant.ReferenceClassDescImpl; import jdk.internal.foreign.abi.NativeEntryPoint; -import jdk.internal.org.objectweb.asm.ClassWriter; import jdk.internal.reflect.CallerSensitive; import jdk.internal.reflect.Reflection; import jdk.internal.vm.annotation.ForceInline; @@ -39,6 +40,8 @@ import sun.invoke.util.VerifyType; import sun.invoke.util.Wrapper; +import java.lang.classfile.ClassFile; +import java.lang.constant.ClassDesc; import java.lang.invoke.MethodHandles.Lookup; import java.lang.reflect.Array; import java.lang.reflect.Constructor; @@ -56,13 +59,14 @@ import java.util.function.Function; import java.util.stream.Stream; +import static java.lang.classfile.ClassFile.*; +import static java.lang.constant.ConstantDescs.*; import static java.lang.invoke.LambdaForm.*; import static java.lang.invoke.MethodHandleNatives.Constants.MN_CALLER_SENSITIVE; import static java.lang.invoke.MethodHandleNatives.Constants.MN_HIDDEN_MEMBER; import static java.lang.invoke.MethodHandleStatics.*; import static java.lang.invoke.MethodHandles.Lookup.IMPL_LOOKUP; import static java.lang.invoke.MethodHandles.Lookup.ClassOption.NESTMATE; -import static jdk.internal.org.objectweb.asm.Opcodes.*; /** * Trusted implementation code for MethodHandle. @@ -1035,8 +1039,10 @@ static MethodHandle bindCaller(MethodHandle mh, Class hostClass) { // Put the whole mess into its own nested class. // That way we can lazily load the code and set up the constants. private static class BindCaller { - private static MethodType INVOKER_MT = MethodType.methodType(Object.class, MethodHandle.class, Object[].class); - private static MethodType REFLECT_INVOKER_MT = MethodType.methodType(Object.class, MethodHandle.class, Object.class, Object[].class); + + private static final ClassDesc CD_Object_array = ReferenceClassDescImpl.ofValidated("[Ljava/lang/Object;"); + private static final MethodType INVOKER_MT = MethodType.methodType(Object.class, MethodHandle.class, Object[].class); + private static final MethodType REFLECT_INVOKER_MT = MethodType.methodType(Object.class, MethodHandle.class, Object.class, Object[].class); static MethodHandle bindCaller(MethodHandle mh, Class hostClass) { // Code in the boot layer should now be careful while creating method handles or @@ -1250,8 +1256,6 @@ private static boolean checkCallerClass(Class expected) { /** Produces byte code for a class that is used as an injected invoker. */ private static byte[] generateInvokerTemplate() { - ClassWriter cw = new ClassWriter(0); - // private static class InjectedInvoker { // /* this is used to wrap DMH(s) of caller-sensitive methods */ // @Hidden @@ -1265,39 +1269,25 @@ private static byte[] generateInvokerTemplate() { // } // } // } - cw.visit(CLASSFILE_VERSION, ACC_PRIVATE | ACC_SUPER, "InjectedInvoker", null, "java/lang/Object", null); - { - var mv = cw.visitMethod(ACC_STATIC, "invoke_V", - "(Ljava/lang/invoke/MethodHandle;[Ljava/lang/Object;)Ljava/lang/Object;", - null, null); - - mv.visitCode(); - mv.visitVarInsn(ALOAD, 0); - mv.visitVarInsn(ALOAD, 1); - mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/invoke/MethodHandle", "invokeExact", - "([Ljava/lang/Object;)Ljava/lang/Object;", false); - mv.visitInsn(ARETURN); - mv.visitMaxs(2, 2); - mv.visitEnd(); - - cw.visitEnd(); - } - - { - var mv = cw.visitMethod(ACC_STATIC, "reflect_invoke_V", - "(Ljava/lang/invoke/MethodHandle;Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;", - null, null); - mv.visitCode(); - mv.visitVarInsn(ALOAD, 0); - mv.visitVarInsn(ALOAD, 1); - mv.visitVarInsn(ALOAD, 2); - mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/invoke/MethodHandle", "invokeExact", - "(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;", false); - mv.visitInsn(ARETURN); - mv.visitMaxs(3, 3); - mv.visitEnd(); - } - return cw.toByteArray(); + return ClassFile.of().build(ReferenceClassDescImpl.ofValidated("LInjectedInvoker;"), clb -> clb + .withFlags(ACC_PRIVATE | ACC_SUPER) + .withMethodBody( + "invoke_V", + MethodTypeDescImpl.ofValidated(CD_Object, CD_MethodHandle, CD_Object_array), + ACC_STATIC, + cob -> cob.aload(0) + .aload(1) + .invokevirtual(CD_MethodHandle, "invokeExact", MethodTypeDescImpl.ofValidated(CD_Object, CD_Object_array)) + .areturn()) + .withMethodBody( + "reflect_invoke_V", + MethodTypeDescImpl.ofValidated(CD_Object, CD_MethodHandle, CD_Object, CD_Object_array), + ACC_STATIC, + cob -> cob.aload(0) + .aload(1) + .aload(2) + .invokevirtual(CD_MethodHandle, "invokeExact", MethodTypeDescImpl.ofValidated(CD_Object, CD_Object, CD_Object_array)) + .areturn())); } } diff --git a/src/java.base/share/classes/java/lang/invoke/MethodHandles.java b/src/java.base/share/classes/java/lang/invoke/MethodHandles.java index 9ac8e42a2f0..0cb77f632b3 100644 --- a/src/java.base/share/classes/java/lang/invoke/MethodHandles.java +++ b/src/java.base/share/classes/java/lang/invoke/MethodHandles.java @@ -28,9 +28,6 @@ import jdk.internal.access.SharedSecrets; import jdk.internal.misc.Unsafe; import jdk.internal.misc.VM; -import jdk.internal.org.objectweb.asm.ClassReader; -import jdk.internal.org.objectweb.asm.Opcodes; -import jdk.internal.org.objectweb.asm.Type; import jdk.internal.reflect.CallerSensitive; import jdk.internal.reflect.CallerSensitiveAdapter; import jdk.internal.reflect.Reflection; @@ -42,8 +39,10 @@ import sun.reflect.misc.ReflectUtil; import sun.security.util.SecurityConstants; +import java.lang.classfile.ClassModel; import java.lang.constant.ConstantDescs; import java.lang.invoke.LambdaForm.BasicType; +import java.lang.invoke.MethodHandleImpl.Intrinsic; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Member; @@ -62,8 +61,8 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Stream; +import static java.lang.classfile.ClassFile.*; import static java.lang.invoke.LambdaForm.BasicType.V_TYPE; -import static java.lang.invoke.MethodHandleImpl.Intrinsic; import static java.lang.invoke.MethodHandleNatives.Constants.*; import static java.lang.invoke.MethodHandleStatics.UNSAFE; import static java.lang.invoke.MethodHandleStatics.newIllegalArgumentException; @@ -2288,27 +2287,16 @@ private static ClassFile readClassFile(byte[] bytes) { String name; int accessFlags; try { - ClassReader reader = new ClassReader(bytes); - // ClassReader does not check if `this_class` is CONSTANT_Class_info - // workaround to read `this_class` using readConst and validate the value - int thisClass = reader.readUnsignedShort(reader.header + 2); - Object constant = reader.readConst(thisClass, new char[reader.getMaxStringLength()]); - if (!(constant instanceof Type type)) { - throw new ClassFormatError("this_class item: #" + thisClass + " not a CONSTANT_Class_info"); - } - if (!type.getDescriptor().startsWith("L")) { - throw new ClassFormatError("this_class item: #" + thisClass + " not a CONSTANT_Class_info"); - } - name = type.getInternalName(); - accessFlags = reader.readUnsignedShort(reader.header); - } catch (RuntimeException e) { - // ASM exceptions are poorly specified + ClassModel cm = java.lang.classfile.ClassFile.of().parse(bytes); + name = cm.thisClass().asInternalName(); + accessFlags = cm.flags().flagsMask(); + } catch (IllegalArgumentException e) { ClassFormatError cfe = new ClassFormatError(); cfe.initCause(e); throw cfe; } // must be a class or interface - if ((accessFlags & Opcodes.ACC_MODULE) != 0) { + if ((accessFlags & ACC_MODULE) != 0) { throw newIllegalArgumentException("Not a class or interface: ACC_MODULE flag is set"); } return new ClassFile(name, accessFlags, bytes); diff --git a/src/java.base/share/classes/java/lang/invoke/MethodType.java b/src/java.base/share/classes/java/lang/invoke/MethodType.java index 29a338261e9..faf8302206e 100644 --- a/src/java.base/share/classes/java/lang/invoke/MethodType.java +++ b/src/java.base/share/classes/java/lang/invoke/MethodType.java @@ -1291,15 +1291,21 @@ static String toFieldDescriptorString(Class cls) { */ @Override public Optional describeConstable() { - try { - return Optional.of(MethodTypeDesc.of(returnType().describeConstable().orElseThrow(), - Stream.of(parameterArray()) - .map(p -> p.describeConstable().orElseThrow()) - .toArray(ClassDesc[]::new))); - } - catch (NoSuchElementException e) { + var retDesc = returnType().describeConstable(); + if (retDesc.isEmpty()) return Optional.empty(); + + if (parameterCount() == 0) + return Optional.of(MethodTypeDesc.of(retDesc.get())); + + var params = new ClassDesc[parameterCount()]; + for (int i = 0; i < params.length; i++) { + var paramDesc = parameterType(i).describeConstable(); + if (paramDesc.isEmpty()) + return Optional.empty(); + params[i] = paramDesc.get(); } + return Optional.of(MethodTypeDesc.of(retDesc.get(), params)); } //--- Serialization. diff --git a/src/java.base/share/classes/java/lang/invoke/TypeConvertingMethodAdapter.java b/src/java.base/share/classes/java/lang/invoke/TypeConvertingMethodAdapter.java index e49094073c3..e35271dc8b4 100644 --- a/src/java.base/share/classes/java/lang/invoke/TypeConvertingMethodAdapter.java +++ b/src/java.base/share/classes/java/lang/invoke/TypeConvertingMethodAdapter.java @@ -25,176 +25,103 @@ package java.lang.invoke; -import jdk.internal.org.objectweb.asm.MethodVisitor; -import jdk.internal.org.objectweb.asm.Opcodes; -import jdk.internal.org.objectweb.asm.Type; -import sun.invoke.util.BytecodeDescriptor; +import java.lang.constant.ClassDesc; +import java.lang.classfile.CodeBuilder; +import java.lang.classfile.TypeKind; +import java.lang.classfile.constantpool.ConstantPoolBuilder; +import java.lang.classfile.constantpool.MethodRefEntry; +import jdk.internal.constant.MethodTypeDescImpl; +import jdk.internal.constant.ReferenceClassDescImpl; import sun.invoke.util.Wrapper; -import static sun.invoke.util.Wrapper.*; -class TypeConvertingMethodAdapter extends MethodVisitor { +import static java.lang.constant.ConstantDescs.*; - TypeConvertingMethodAdapter(MethodVisitor mv) { - super(Opcodes.ASM7, mv); - } - - private static final int NUM_WRAPPERS = Wrapper.COUNT; - - private static final String NAME_OBJECT = "java/lang/Object"; - private static final String WRAPPER_PREFIX = "Ljava/lang/"; - - // Same for all primitives; name of the boxing method - private static final String NAME_BOX_METHOD = "valueOf"; - - // Table of opcodes for widening primitive conversions; NOP = no conversion - private static final int[][] wideningOpcodes = new int[NUM_WRAPPERS][NUM_WRAPPERS]; - - private static final Wrapper[] FROM_WRAPPER_NAME = new Wrapper[16]; - - // Table of wrappers for primitives, indexed by ASM type sorts - private static final Wrapper[] FROM_TYPE_SORT = new Wrapper[12]; - - static { - for (Wrapper w : Wrapper.values()) { - if (w.basicTypeChar() != 'L') { - int wi = hashWrapperName(w.wrapperSimpleName()); - assert (FROM_WRAPPER_NAME[wi] == null); - FROM_WRAPPER_NAME[wi] = w; - } - } - - // wideningOpcodes[][] will be NOP-initialized by default - assert(Opcodes.NOP == 0); +class TypeConvertingMethodAdapter { - initWidening(LONG, Opcodes.I2L, BYTE, SHORT, INT, CHAR); - initWidening(LONG, Opcodes.F2L, FLOAT); - initWidening(FLOAT, Opcodes.I2F, BYTE, SHORT, INT, CHAR); - initWidening(FLOAT, Opcodes.L2F, LONG); - initWidening(DOUBLE, Opcodes.I2D, BYTE, SHORT, INT, CHAR); - initWidening(DOUBLE, Opcodes.F2D, FLOAT); - initWidening(DOUBLE, Opcodes.L2D, LONG); + private static class BoxHolder { + private static final ConstantPoolBuilder CP = ConstantPoolBuilder.of(); - FROM_TYPE_SORT[Type.BYTE] = Wrapper.BYTE; - FROM_TYPE_SORT[Type.SHORT] = Wrapper.SHORT; - FROM_TYPE_SORT[Type.INT] = Wrapper.INT; - FROM_TYPE_SORT[Type.LONG] = Wrapper.LONG; - FROM_TYPE_SORT[Type.CHAR] = Wrapper.CHAR; - FROM_TYPE_SORT[Type.FLOAT] = Wrapper.FLOAT; - FROM_TYPE_SORT[Type.DOUBLE] = Wrapper.DOUBLE; - FROM_TYPE_SORT[Type.BOOLEAN] = Wrapper.BOOLEAN; - } - - private static void initWidening(Wrapper to, int opcode, Wrapper... from) { - for (Wrapper f : from) { - wideningOpcodes[f.ordinal()][to.ordinal()] = opcode; + private static MethodRefEntry box(ClassDesc primitive, ClassDesc target) { + return CP.methodRefEntry(target, "valueOf", MethodTypeDescImpl.ofValidated(target, primitive)); } - } - /** - * Class name to Wrapper hash, derived from Wrapper.hashWrap() - * @param xn - * @return The hash code 0-15 - */ - private static int hashWrapperName(String xn) { - if (xn.length() < 3) { - return 0; + private static final MethodRefEntry BOX_BOOLEAN = box(CD_boolean, CD_Boolean), + BOX_BYTE = box(CD_byte, CD_Byte), + BOX_SHORT = box(CD_short, CD_Short), + BOX_CHAR = box(CD_char, CD_Character), + BOX_INT = box(CD_int, CD_Integer), + BOX_LONG = box(CD_long, CD_Long), + BOX_FLOAT = box(CD_float, CD_Float), + BOX_DOUBLE = box(CD_double, CD_Double); + + private static MethodRefEntry unbox(ClassDesc owner, String methodName, ClassDesc primitiveTarget) { + return CP.methodRefEntry(owner, methodName, MethodTypeDescImpl.ofValidated(primitiveTarget)); } - return (3 * xn.charAt(1) + xn.charAt(2)) % 16; - } - - private Wrapper wrapperOrNullFromDescriptor(String desc) { - if (!desc.startsWith(WRAPPER_PREFIX)) { - // Not a class type (array or method), so not a boxed type - // or not in the right package - return null; - } - // Pare it down to the simple class name - String cname = desc.substring(WRAPPER_PREFIX.length(), desc.length() - 1); - // Hash to a Wrapper - Wrapper w = FROM_WRAPPER_NAME[hashWrapperName(cname)]; - if (w == null || w.wrapperSimpleName().equals(cname)) { - return w; - } else { - return null; - } - } - private static String wrapperName(Wrapper w) { - return "java/lang/" + w.wrapperSimpleName(); + private static final MethodRefEntry UNBOX_BOOLEAN = unbox(CD_Boolean, "booleanValue", CD_boolean), + UNBOX_BYTE = unbox(CD_Number, "byteValue", CD_byte), + UNBOX_SHORT = unbox(CD_Number, "shortValue", CD_short), + UNBOX_CHAR = unbox(CD_Character, "charValue", CD_char), + UNBOX_INT = unbox(CD_Number, "intValue", CD_int), + UNBOX_LONG = unbox(CD_Number, "longValue", CD_long), + UNBOX_FLOAT = unbox(CD_Number, "floatValue", CD_float), + UNBOX_DOUBLE = unbox(CD_Number, "doubleValue", CD_double); } - private static String unboxMethod(Wrapper w) { - return w.primitiveSimpleName() + "Value"; + private static TypeKind primitiveTypeKindFromClass(Class type) { + if (type == int.class) return TypeKind.IntType; + if (type == long.class) return TypeKind.LongType; + if (type == boolean.class) return TypeKind.BooleanType; + if (type == short.class) return TypeKind.ShortType; + if (type == byte.class) return TypeKind.ByteType; + if (type == char.class) return TypeKind.CharType; + if (type == float.class) return TypeKind.FloatType; + if (type == double.class) return TypeKind.DoubleType; + return null; } - private static String boxingDescriptor(Wrapper w) { - return "(" + w.basicTypeChar() + ")L" + wrapperName(w) + ";"; + static void boxIfTypePrimitive(CodeBuilder cob, TypeKind tk) { + box(cob, tk); } - private static String unboxingDescriptor(Wrapper w) { - return "()" + w.basicTypeChar(); - } - - void boxIfTypePrimitive(Type t) { - Wrapper w = FROM_TYPE_SORT[t.getSort()]; - if (w != null) { - box(w); - } - } - - void widen(Wrapper ws, Wrapper wt) { + static void widen(CodeBuilder cob, TypeKind ws, TypeKind wt) { + ws = ws.asLoadable(); + wt = wt.asLoadable(); if (ws != wt) { - int opcode = wideningOpcodes[ws.ordinal()][wt.ordinal()]; - if (opcode != Opcodes.NOP) { - visitInsn(opcode); - } + cob.conversion(ws, wt); } } - void box(Wrapper w) { - visitMethodInsn(Opcodes.INVOKESTATIC, - wrapperName(w), - NAME_BOX_METHOD, - boxingDescriptor(w), false); - } - - /** - * Convert types by unboxing. The source type is known to be a primitive wrapper. - * @param sname A primitive wrapper corresponding to wrapped reference source type - * @param wt A primitive wrapper being converted to - */ - void unbox(String sname, Wrapper wt) { - visitMethodInsn(Opcodes.INVOKEVIRTUAL, - sname, - unboxMethod(wt), - unboxingDescriptor(wt), false); - } - - private String descriptorToName(String desc) { - int last = desc.length() - 1; - if (desc.charAt(0) == 'L' && desc.charAt(last) == ';') { - // In descriptor form - return desc.substring(1, last); - } else { - // Already in internal name form - return desc; + static void box(CodeBuilder cob, TypeKind tk) { + switch (tk) { + case BooleanType -> cob.invokestatic(BoxHolder.BOX_BOOLEAN); + case ByteType -> cob.invokestatic(BoxHolder.BOX_BYTE); + case CharType -> cob.invokestatic(BoxHolder.BOX_CHAR); + case DoubleType -> cob.invokestatic(BoxHolder.BOX_DOUBLE); + case FloatType -> cob.invokestatic(BoxHolder.BOX_FLOAT); + case IntType -> cob.invokestatic(BoxHolder.BOX_INT); + case LongType -> cob.invokestatic(BoxHolder.BOX_LONG); + case ShortType -> cob.invokestatic(BoxHolder.BOX_SHORT); } } - void cast(String ds, String dt) { - String ns = descriptorToName(ds); - String nt = descriptorToName(dt); - if (!nt.equals(ns) && !nt.equals(NAME_OBJECT)) { - visitTypeInsn(Opcodes.CHECKCAST, nt); + static void unbox(CodeBuilder cob, TypeKind to) { + switch (to) { + case BooleanType -> cob.invokevirtual(BoxHolder.UNBOX_BOOLEAN); + case ByteType -> cob.invokevirtual(BoxHolder.UNBOX_BYTE); + case CharType -> cob.invokevirtual(BoxHolder.UNBOX_CHAR); + case DoubleType -> cob.invokevirtual(BoxHolder.UNBOX_DOUBLE); + case FloatType -> cob.invokevirtual(BoxHolder.UNBOX_FLOAT); + case IntType -> cob.invokevirtual(BoxHolder.UNBOX_INT); + case LongType -> cob.invokevirtual(BoxHolder.UNBOX_LONG); + case ShortType -> cob.invokevirtual(BoxHolder.UNBOX_SHORT); } } - private Wrapper toWrapper(String desc) { - char first = desc.charAt(0); - if (first == '[' || first == '(') { - first = 'L'; + static void cast(CodeBuilder cob, ClassDesc dt) { + if (!dt.equals(CD_Object)) { + cob.checkcast(dt); } - return Wrapper.forBasicType(first); } /** @@ -204,7 +131,7 @@ private Wrapper toWrapper(String desc) { * @param target * @param functional */ - void convertType(Class arg, Class target, Class functional) { + static void convertType(CodeBuilder cob, Class arg, Class target, Class functional) { if (arg.equals(target) && arg.equals(functional)) { return; } @@ -212,84 +139,69 @@ void convertType(Class arg, Class target, Class functional) { return; } if (arg.isPrimitive()) { - Wrapper wArg = Wrapper.forPrimitiveType(arg); if (target.isPrimitive()) { // Both primitives: widening - widen(wArg, Wrapper.forPrimitiveType(target)); + widen(cob, TypeKind.from(arg), TypeKind.from(target)); } else { // Primitive argument to reference target - String dTarget = BytecodeDescriptor.unparse(target); - Wrapper wPrimTarget = wrapperOrNullFromDescriptor(dTarget); - if (wPrimTarget != null) { + TypeKind wPrimTk = primitiveTypeKindFromClass(target); + if (wPrimTk != null) { // The target is a boxed primitive type, widen to get there before boxing - widen(wArg, wPrimTarget); - box(wPrimTarget); + widen(cob, TypeKind.from(arg), wPrimTk); + box(cob, wPrimTk); } else { // Otherwise, box and cast - box(wArg); - cast(wrapperName(wArg), dTarget); + box(cob, TypeKind.from(arg)); + cast(cob, classDesc(target)); } } } else { - String dArg = BytecodeDescriptor.unparse(arg); - String dSrc; - if (functional.isPrimitive()) { - dSrc = dArg; + Class src; + if (arg == functional || functional.isPrimitive()) { + src = arg; } else { // Cast to convert to possibly more specific type, and generate CCE for invalid arg - dSrc = BytecodeDescriptor.unparse(functional); - cast(dArg, dSrc); + src = functional; + cast(cob, classDesc(functional)); } - String dTarget = BytecodeDescriptor.unparse(target); if (target.isPrimitive()) { - Wrapper wTarget = toWrapper(dTarget); // Reference argument to primitive target - Wrapper wps = wrapperOrNullFromDescriptor(dSrc); + TypeKind wps = primitiveTypeKindFromClass(src); if (wps != null) { - if (wps.isSigned() || wps.isFloating()) { + if (src != Character.class && src != Boolean.class) { // Boxed number to primitive - unbox(wrapperName(wps), wTarget); + unbox(cob, TypeKind.from(target)); } else { // Character or Boolean - unbox(wrapperName(wps), wps); - widen(wps, wTarget); + unbox(cob, wps); + widen(cob, wps, TypeKind.from(target)); } } else { // Source type is reference type, but not boxed type, // assume it is super type of target type - String intermediate; - if (wTarget.isSigned() || wTarget.isFloating()) { - // Boxed number to primitive - intermediate = "java/lang/Number"; + if (target == char.class) { + cast(cob, CD_Character); + } else if (target == boolean.class) { + cast(cob, CD_Boolean); } else { - // Character or Boolean - intermediate = wrapperName(wTarget); + // Boxed number to primitive + cast(cob, CD_Number); } - cast(dSrc, intermediate); - unbox(intermediate, wTarget); + unbox(cob, TypeKind.from(target)); } } else { // Both reference types: just case to target type - cast(dSrc, dTarget); + if (src != target) { + cast(cob, classDesc(target)); + } } } } - /** - * The following method is copied from - * org.objectweb.asm.commons.InstructionAdapter. Part of ASM: a very small - * and fast Java bytecode manipulation framework. - * Copyright (c) 2000-2005 INRIA, France Telecom All rights reserved. - */ - void iconst(final int cst) { - if (cst >= -1 && cst <= 5) { - mv.visitInsn(Opcodes.ICONST_0 + cst); - } else if (cst >= Byte.MIN_VALUE && cst <= Byte.MAX_VALUE) { - mv.visitIntInsn(Opcodes.BIPUSH, cst); - } else if (cst >= Short.MIN_VALUE && cst <= Short.MAX_VALUE) { - mv.visitIntInsn(Opcodes.SIPUSH, cst); - } else { - mv.visitLdcInsn(cst); - } + static ClassDesc classDesc(Class cls) { + return cls.isPrimitive() ? Wrapper.forPrimitiveType(cls).basicClassDescriptor() + : cls == Object.class ? CD_Object + : cls == String.class ? CD_String + : ReferenceClassDescImpl.ofValidated(cls.descriptorString()); } } diff --git a/src/java.base/share/classes/java/lang/reflect/ProxyGenerator.java b/src/java.base/share/classes/java/lang/reflect/ProxyGenerator.java index a484c191206..6c82a6ecb6f 100644 --- a/src/java.base/share/classes/java/lang/reflect/ProxyGenerator.java +++ b/src/java.base/share/classes/java/lang/reflect/ProxyGenerator.java @@ -47,13 +47,10 @@ import static java.lang.classfile.ClassFile.*; import java.lang.classfile.attribute.StackMapFrameInfo; import java.lang.classfile.attribute.StackMapTableAttribute; -import java.lang.constant.ConstantDescs; + import static java.lang.constant.ConstantDescs.*; import static jdk.internal.constant.ConstantUtils.*; -import java.lang.constant.DirectMethodHandleDesc; -import java.lang.constant.DynamicConstantDesc; - /** * ProxyGenerator contains the code to generate a dynamic proxy class * for the java.lang.reflect.Proxy API. @@ -67,7 +64,10 @@ final class ProxyGenerator { ClassFile.of(ClassFile.StackMapsOption.DROP_STACK_MAPS); private static final ClassDesc + CD_ClassLoader = ReferenceClassDescImpl.ofValidated("Ljava/lang/ClassLoader;"), CD_Class_array = ReferenceClassDescImpl.ofValidated("[Ljava/lang/Class;"), + CD_ClassNotFoundException = ReferenceClassDescImpl.ofValidated("Ljava/lang/ClassNotFoundException;"), + CD_NoClassDefFoundError = ReferenceClassDescImpl.ofValidated("Ljava/lang/NoClassDefFoundError;"), CD_IllegalAccessException = ReferenceClassDescImpl.ofValidated("Ljava/lang/IllegalAccessException;"), CD_InvocationHandler = ReferenceClassDescImpl.ofValidated("Ljava/lang/reflect/InvocationHandler;"), CD_Method = ReferenceClassDescImpl.ofValidated("Ljava/lang/reflect/Method;"), @@ -83,8 +83,9 @@ final class ProxyGenerator { MTD_void_String = MethodTypeDescImpl.ofValidated(CD_void, CD_String), MTD_void_Throwable = MethodTypeDescImpl.ofValidated(CD_void, CD_Throwable), MTD_Class = MethodTypeDescImpl.ofValidated(CD_Class), - MTD_Class_array = MethodTypeDescImpl.ofValidated(CD_Class_array), - MTD_Method_String_Class_array = MethodTypeDescImpl.ofValidated(CD_Method, ConstantDescs.CD_String, CD_Class_array), + MTD_Class_String_boolean_ClassLoader = MethodTypeDescImpl.ofValidated(CD_Class, CD_String, CD_boolean, CD_ClassLoader), + MTD_ClassLoader = MethodTypeDescImpl.ofValidated(CD_ClassLoader), + MTD_Method_String_Class_array = MethodTypeDescImpl.ofValidated(CD_Method, CD_String, CD_Class_array), MTD_MethodHandles$Lookup = MethodTypeDescImpl.ofValidated(CD_MethodHandles_Lookup), MTD_MethodHandles$Lookup_MethodHandles$Lookup = MethodTypeDescImpl.ofValidated(CD_MethodHandles_Lookup, CD_MethodHandles_Lookup), MTD_Object_Object_Method_ObjectArray = MethodTypeDescImpl.ofValidated(CD_Object, CD_Object, CD_Method, CD_Object_array), @@ -109,34 +110,33 @@ final class ProxyGenerator { "jdk.proxy.ProxyGenerator.saveGeneratedFiles")); /* Preloaded ProxyMethod objects for methods in java.lang.Object */ - private static final ProxyMethod HASH_CODE_METHOD; - private static final ProxyMethod EQUALS_METHOD; - private static final ProxyMethod TO_STRING_METHOD; + private static final Method OBJECT_HASH_CODE_METHOD; + private static final Method OBJECT_EQUALS_METHOD; + private static final Method OBJECT_TO_STRING_METHOD; static { try { - HASH_CODE_METHOD = new ProxyMethod(Object.class.getMethod("hashCode")); - EQUALS_METHOD = new ProxyMethod(Object.class.getMethod("equals", Object.class)); - TO_STRING_METHOD = new ProxyMethod(Object.class.getMethod("toString")); + OBJECT_HASH_CODE_METHOD = Object.class.getMethod("hashCode"); + OBJECT_EQUALS_METHOD = Object.class.getMethod("equals", Object.class); + OBJECT_TO_STRING_METHOD = Object.class.getMethod("toString"); } catch (NoSuchMethodException e) { throw new NoSuchMethodError(e.getMessage()); } } private final ConstantPoolBuilder cp; - private final List throwableStack; + private final List classLoaderLocal, throwableStack; private final NameAndTypeEntry exInit; - private final ClassEntry object, proxy, ute; + private final ClassEntry objectCE, proxyCE, uteCE, classCE; private final FieldRefEntry handlerField; - private final InterfaceMethodRefEntry invoke; - private final MethodRefEntry uteInit; - private final DirectMethodHandleDesc bsm; + private final InterfaceMethodRefEntry invocationHandlerInvoke; + private final MethodRefEntry uteInit, classGetMethod, classForName, throwableGetMessage; /** - * Name of proxy class + * ClassEntry for this proxy class */ - private final ClassEntry classEntry; + private final ClassEntry thisClassCE; /** * Proxy interfaces @@ -155,6 +155,12 @@ final class ProxyGenerator { */ private final Map> proxyMethods = new LinkedHashMap<>(); + /** + * Ordinal of next ProxyMethod object added to proxyMethods. + * Indexes are reserved for hashcode(0), equals(1), toString(2). + */ + private int proxyMethodCount = 3; + /** * Construct a ProxyGenerator to generate a proxy class with the * specified name and for the given interfaces. @@ -165,18 +171,23 @@ final class ProxyGenerator { private ProxyGenerator(String className, List> interfaces, int accessFlags) { this.cp = ConstantPoolBuilder.of(); - this.classEntry = cp.classEntry(ConstantUtils.binaryNameToDesc(className)); + this.thisClassCE = cp.classEntry(ConstantUtils.binaryNameToDesc(className)); this.interfaces = interfaces; this.accessFlags = accessFlags; - this.throwableStack = List.of(StackMapFrameInfo.ObjectVerificationTypeInfo.of(cp.classEntry(CD_Throwable))); + var throwable = cp.classEntry(CD_Throwable); + this.classLoaderLocal = List.of(StackMapFrameInfo.ObjectVerificationTypeInfo.of(cp.classEntry(CD_ClassLoader))); + this.throwableStack = List.of(StackMapFrameInfo.ObjectVerificationTypeInfo.of(throwable)); this.exInit = cp.nameAndTypeEntry(INIT_NAME, MTD_void_String); - this.object = cp.classEntry(CD_Object); - this.proxy = cp.classEntry(CD_Proxy); - this.handlerField = cp.fieldRefEntry(proxy, cp.nameAndTypeEntry(NAME_HANDLER_FIELD, CD_InvocationHandler)); - this.invoke = cp.interfaceMethodRefEntry(CD_InvocationHandler, "invoke", MTD_Object_Object_Method_ObjectArray); - this.ute = cp.classEntry(CD_UndeclaredThrowableException); - this.uteInit = cp.methodRefEntry(ute, cp.nameAndTypeEntry(INIT_NAME, MTD_void_Throwable)); - this.bsm = ConstantDescs.ofConstantBootstrap(classEntry.asSymbol(), "$getMethod", CD_Method, CD_Class, CD_String, CD_MethodType); + this.objectCE = cp.classEntry(CD_Object); + this.proxyCE = cp.classEntry(CD_Proxy); + this.classCE = cp.classEntry(CD_Class); + this.handlerField = cp.fieldRefEntry(proxyCE, cp.nameAndTypeEntry(NAME_HANDLER_FIELD, CD_InvocationHandler)); + this.invocationHandlerInvoke = cp.interfaceMethodRefEntry(CD_InvocationHandler, "invoke", MTD_Object_Object_Method_ObjectArray); + this.uteCE = cp.classEntry(CD_UndeclaredThrowableException); + this.uteInit = cp.methodRefEntry(uteCE, cp.nameAndTypeEntry(INIT_NAME, MTD_void_Throwable)); + this.classGetMethod = cp.methodRefEntry(classCE, cp.nameAndTypeEntry("getMethod", MTD_Method_String_Class_array)); + this.classForName = cp.methodRefEntry(classCE, cp.nameAndTypeEntry("forName", MTD_Class_String_boolean_ClassLoader)); + this.throwableGetMessage = cp.methodRefEntry(throwable, cp.nameAndTypeEntry("getMessage", MTD_String)); } /** @@ -435,9 +446,9 @@ private byte[] generateClassFile() { * java.lang.Object take precedence over duplicate methods in the * proxy interfaces. */ - addProxyMethod(HASH_CODE_METHOD); - addProxyMethod(EQUALS_METHOD); - addProxyMethod(TO_STRING_METHOD); + addProxyMethod(new ProxyMethod(OBJECT_HASH_CODE_METHOD, "m0")); + addProxyMethod(new ProxyMethod(OBJECT_EQUALS_METHOD, "m1")); + addProxyMethod(new ProxyMethod(OBJECT_TO_STRING_METHOD, "m2")); /* * Accumulate all of the methods from the proxy interfaces. @@ -458,20 +469,23 @@ private byte[] generateClassFile() { checkReturnTypes(sigmethods); } - return CF_CONTEXT.build(classEntry, cp, clb -> { - clb.withSuperclass(proxy); + return CF_CONTEXT.build(thisClassCE, cp, clb -> { + clb.withSuperclass(proxyCE); clb.withFlags(accessFlags); clb.withInterfaces(toClassEntries(cp, interfaces)); generateConstructor(clb); for (List sigmethods : proxyMethods.values()) { for (ProxyMethod pm : sigmethods) { + // add static field for the Method object + clb.withField(pm.methodFieldName, CD_Method, ACC_PRIVATE | ACC_STATIC | ACC_FINAL); + // Generate code for proxy method - pm.generateMethod(this, clb); + pm.generateMethod(clb); } } - generateBootstrapMethod(clb); + generateStaticInitializer(clb); generateLookupAccessor(clb); }); } @@ -514,7 +528,7 @@ private void addProxyMethod(Method m, Class fromClass) { } } sigmethods.add(new ProxyMethod(m, sig, m.getSharedParameterTypes(), returnType, - exceptionTypes, fromClass)); + exceptionTypes, fromClass, "m" + proxyMethodCount++)); } /** @@ -536,32 +550,56 @@ private void generateConstructor(ClassBuilder clb) { clb.withMethodBody(INIT_NAME, MTD_void_InvocationHandler, ACC_PUBLIC, cob -> cob .aload(0) .aload(1) - .invokespecial(cp.methodRefEntry(proxy, cp.nameAndTypeEntry(INIT_NAME, MTD_void_InvocationHandler))) + .invokespecial(cp.methodRefEntry(proxyCE, + cp.nameAndTypeEntry(INIT_NAME, MTD_void_InvocationHandler))) .return_()); } /** - * Generate CONDY bootstrap method for the proxy class to retrieve {@link Method} instances. + * Generate the class initializer. + * Discussion: Currently, for Proxy to work with SecurityManager, + * we rely on the parameter classes of the methods to be computed + * from Proxy instead of via user code paths like bootstrap method + * lazy evaluation. That might change if we can pass in the live + * Method objects directly.. */ - private void generateBootstrapMethod(ClassBuilder clb) { - clb.withMethodBody(bsm.methodName(), bsm.invocationType(), ClassFile.ACC_PRIVATE | ClassFile.ACC_STATIC, cob -> { - cob.aload(3) //interface Class - .aload(4) //interface method name String - .aload(5) //interface MethodType - .invokevirtual(CD_MethodType, "parameterArray", MTD_Class_array) - .invokevirtual(ConstantDescs.CD_Class, "getMethod", MTD_Method_String_Class_array) - .areturn(); - Label failLabel = cob.newBoundLabel(); - ClassEntry nsme = cp.classEntry(CD_NoSuchMethodError); - cob.exceptionCatch(cob.startLabel(), failLabel, failLabel, CD_NoSuchMethodException) - .new_(nsme) + private void generateStaticInitializer(ClassBuilder clb) { + clb.withMethodBody(CLASS_INIT_NAME, MTD_void, ACC_STATIC, cob -> { + // Put ClassLoader at local variable index 0, used by + // Class.forName(String, boolean, ClassLoader) calls + cob.ldc(thisClassCE) + .invokevirtual(cp.methodRefEntry(classCE, + cp.nameAndTypeEntry("getClassLoader", MTD_ClassLoader))) + .astore(0); + var ts = cob.newBoundLabel(); + for (List sigmethods : proxyMethods.values()) { + for (ProxyMethod pm : sigmethods) { + pm.codeFieldInitialization(cob); + } + } + cob.return_(); + var c1 = cob.newBoundLabel(); + var nsmError = cp.classEntry(CD_NoSuchMethodError); + cob.exceptionCatch(ts, c1, c1, CD_NoSuchMethodException) + .new_(nsmError) + .dup_x1() + .swap() + .invokevirtual(throwableGetMessage) + .invokespecial(cp.methodRefEntry(nsmError, exInit)) + .athrow(); + var c2 = cob.newBoundLabel(); + var ncdfError = cp.classEntry(CD_NoClassDefFoundError); + cob.exceptionCatch(ts, c1, c2, CD_ClassNotFoundException) + .new_(ncdfError) .dup_x1() .swap() - .invokevirtual(cp.methodRefEntry(CD_Throwable, "getMessage", MTD_String)) - .invokespecial(cp.methodRefEntry(nsme, exInit)) - .athrow() - .with(StackMapTableAttribute.of(List.of( - StackMapFrameInfo.of(failLabel, List.of(), throwableStack)))); + .invokevirtual(throwableGetMessage) + .invokespecial(cp.methodRefEntry(ncdfError, exInit)) + .athrow(); + cob.with(StackMapTableAttribute.of(List.of( + StackMapFrameInfo.of(c1, classLoaderLocal, throwableStack), + StackMapFrameInfo.of(c2, classLoaderLocal, throwableStack)))); + }); } @@ -581,7 +619,7 @@ private void generateLookupAccessor(ClassBuilder clb) { ClassEntry iae = cp.classEntry(CD_IllegalAccessException); cob.aload(cob.parameterSlot(0)) .invokevirtual(cp.methodRefEntry(mhl, cp.nameAndTypeEntry("lookupClass", MTD_Class))) - .ldc(proxy) + .ldc(proxyCE) .if_acmpne(failLabel) .aload(cob.parameterSlot(0)) .invokevirtual(cp.methodRefEntry(mhl, cp.nameAndTypeEntry("hasFullPrivilegeAccess", MTD_boolean))) @@ -607,24 +645,29 @@ private void generateLookupAccessor(ClassBuilder clb) { * being generated: a method whose implementation will encode and * dispatch invocations to the proxy instance's invocation handler. */ - private static class ProxyMethod { + private class ProxyMethod { private final Method method; private final String shortSignature; private final Class fromClass; private final Class[] parameterTypes; private final Class returnType; + private final String methodFieldName; private Class[] exceptionTypes; + private final FieldRefEntry methodField; private ProxyMethod(Method method, String sig, Class[] parameterTypes, Class returnType, Class[] exceptionTypes, - Class fromClass) { + Class fromClass, String methodFieldName) { this.method = method; this.shortSignature = sig; this.parameterTypes = parameterTypes; this.returnType = returnType; this.exceptionTypes = exceptionTypes; this.fromClass = fromClass; + this.methodFieldName = methodFieldName; + this.methodField = cp.fieldRefEntry(thisClassCE, + cp.nameAndTypeEntry(methodFieldName, CD_Method)); } /** @@ -632,17 +675,16 @@ private ProxyMethod(Method method, String sig, Class[] parameterTypes, * * @param method The method for which to create a proxy */ - private ProxyMethod(Method method) { + private ProxyMethod(Method method, String methodFieldName) { this(method, method.toShortSignature(), method.getSharedParameterTypes(), method.getReturnType(), - method.getSharedExceptionTypes(), method.getDeclaringClass()); + method.getSharedExceptionTypes(), method.getDeclaringClass(), methodFieldName); } /** * Generate this method, including the code and exception table entry. */ - private void generateMethod(ProxyGenerator pg, ClassBuilder clb) { - var cp = pg.cp; + private void generateMethod(ClassBuilder clb) { var desc = methodTypeDesc(returnType, parameterTypes); int accessFlags = (method.isVarArgs()) ? ACC_VARARGS | ACC_PUBLIC | ACC_FINAL : ACC_PUBLIC | ACC_FINAL; @@ -650,17 +692,14 @@ private void generateMethod(ProxyGenerator pg, ClassBuilder clb) { clb.withMethod(method.getName(), desc, accessFlags, mb -> mb.with(ExceptionsAttribute.of(toClassEntries(cp, List.of(exceptionTypes)))) .withCode(cob -> { - cob.aload(0) - .getfield(pg.handlerField) - .aload(0) - .ldc(DynamicConstantDesc.of(pg.bsm, - referenceClassDesc(fromClass), - method.getName(), - desc)); + cob.aload(cob.receiverSlot()) + .getfield(handlerField) + .aload(cob.receiverSlot()) + .getstatic(methodField); if (parameterTypes.length > 0) { // Create an array and fill with the parameters converting primitives to wrappers cob.loadConstant(parameterTypes.length) - .anewarray(pg.object); + .anewarray(objectCE); for (int i = 0; i < parameterTypes.length; i++) { cob.dup() .loadConstant(i); @@ -671,7 +710,7 @@ private void generateMethod(ProxyGenerator pg, ClassBuilder clb) { cob.aconst_null(); } - cob.invokeinterface(pg.invoke); + cob.invokeinterface(invocationHandlerInvoke); if (returnType == void.class) { cob.pop() @@ -687,14 +726,14 @@ private void generateMethod(ProxyGenerator pg, ClassBuilder clb) { cob.athrow(); // just rethrow the exception var c2 = cob.newBoundLabel(); cob.exceptionCatchAll(cob.startLabel(), c1, c2) - .new_(pg.ute) + .new_(uteCE) .dup_x1() .swap() - .invokespecial(pg.uteInit) + .invokespecial(uteInit) .athrow() .with(StackMapTableAttribute.of(List.of( - StackMapFrameInfo.of(c1, List.of(), pg.throwableStack), - StackMapFrameInfo.of(c2, List.of(), pg.throwableStack)))); + StackMapFrameInfo.of(c1, List.of(), throwableStack), + StackMapFrameInfo.of(c2, List.of(), throwableStack)))); } })); } @@ -709,7 +748,7 @@ private void codeWrapArgument(CodeBuilder cob, Class type, int slot) { if (type.isPrimitive()) { cob.loadLocal(TypeKind.from(type).asLoadable(), slot); PrimitiveTypeInfo prim = PrimitiveTypeInfo.get(type); - cob.invokestatic(prim.wrapperMethodRef(cob.constantPool())); + cob.invokestatic(prim.wrapperMethodRef(cp)); } else { cob.aload(slot); } @@ -725,7 +764,7 @@ private void codeUnwrapReturnValue(CodeBuilder cob, Class type) { PrimitiveTypeInfo prim = PrimitiveTypeInfo.get(type); cob.checkcast(prim.wrapperClass) - .invokevirtual(prim.unwrapMethodRef(cob.constantPool())) + .invokevirtual(prim.unwrapMethodRef(cp)) .return_(TypeKind.from(type).asLoadable()); } else { cob.checkcast(referenceClassDesc(type)) @@ -733,6 +772,57 @@ private void codeUnwrapReturnValue(CodeBuilder cob, Class type) { } } + /** + * Generate code for initializing the static field that stores + * the Method object for this proxy method. A class loader is + * anticipated at local variable index 0. + * The generated code must be run in an AccessController.doPrivileged + * block if a SecurityManager is present, as otherwise the code + * cannot pass {@code null} ClassLoader to forName. + */ + private void codeFieldInitialization(CodeBuilder cob) { + var cp = cob.constantPool(); + codeClassForName(cob, fromClass); + + cob.ldc(method.getName()) + .loadConstant(parameterTypes.length) + .anewarray(classCE); + + // Construct an array with the parameter types mapping primitives to Wrapper types + for (int i = 0; i < parameterTypes.length; i++) { + cob.dup() + .loadConstant(i); + if (parameterTypes[i].isPrimitive()) { + PrimitiveTypeInfo prim = PrimitiveTypeInfo.get(parameterTypes[i]); + cob.getstatic(prim.typeFieldRef(cp)); + } else { + codeClassForName(cob, parameterTypes[i]); + } + cob.aastore(); + } + // lookup the method + cob.invokevirtual(classGetMethod) + .putstatic(methodField); + } + + /* + * =============== Code Generation Utility Methods =============== + */ + + /** + * Generate code to invoke the Class.forName with the name of the given + * class to get its Class object at runtime. The code is written to + * the supplied stream. Note that the code generated by this method + * may cause the checked ClassNotFoundException to be thrown. A class + * loader is anticipated at local variable index 0. + */ + private void codeClassForName(CodeBuilder cob, Class cl) { + cob.ldc(cl.getName()) + .iconst_0() // false + .aload(0)// classLoader + .invokestatic(classForName); + } + @Override public String toString() { return method.toShortString(); @@ -799,5 +889,9 @@ public MethodRefEntry wrapperMethodRef(ConstantPoolBuilder cp) { public MethodRefEntry unwrapMethodRef(ConstantPoolBuilder cp) { return cp.methodRefEntry(wrapperClass, unwrapMethodName, unwrapMethodType); } + + public FieldRefEntry typeFieldRef(ConstantPoolBuilder cp) { + return cp.fieldRefEntry(wrapperClass, "TYPE", CD_Class); + } } } diff --git a/src/java.base/share/classes/java/util/regex/Pattern.java b/src/java.base/share/classes/java/util/regex/Pattern.java index 62ef125fdee..0e87bebdcbf 100644 --- a/src/java.base/share/classes/java/util/regex/Pattern.java +++ b/src/java.base/share/classes/java/util/regex/Pattern.java @@ -160,7 +160,7 @@ * Any character (may or may not match line terminators) * {@code \d} * A digit: {@code [0-9]} if - * * UNICODE_CHARACTER_CLASS is not set. See Unicode Support. + * UNICODE_CHARACTER_CLASS is not set. See Unicode Support. * {@code \D} * A non-digit: {@code [^0-9]} * {@code \h} @@ -251,8 +251,9 @@ * {@code $} * The end of a line * {@code \b} - * A word boundary: {@code (?:(?<=\w)(?=\W)|(?<=\W)(?=\w))} (the location - * where a non-word character abuts a word character) + * A word boundary: + * at the beginning or at the end of a line if a word character ({@code \w}) appears there; + * or between a word ({@code \w}) and a non-word character ({@code \W}), in either order. * {@code \b{g}} * A Unicode extended grapheme cluster boundary * {@code \B} diff --git a/src/java.base/share/classes/java/util/stream/Gatherer.java b/src/java.base/share/classes/java/util/stream/Gatherer.java index ced746cd673..40c3c682e73 100644 --- a/src/java.base/share/classes/java/util/stream/Gatherer.java +++ b/src/java.base/share/classes/java/util/stream/Gatherer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 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 @@ -293,7 +293,7 @@ static Supplier defaultInitializer() { * * @implSpec This method always returns the same instance. * - * @see Gatherer#finisher() + * @see Gatherer#combiner() * @return the instance of the default combiner * @param the type of the state of the returned combiner */ diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/StackMapGenerator.java b/src/java.base/share/classes/jdk/internal/classfile/impl/StackMapGenerator.java index ddb14b2d26a..0f1f6fb69de 100644 --- a/src/java.base/share/classes/jdk/internal/classfile/impl/StackMapGenerator.java +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/StackMapGenerator.java @@ -25,15 +25,19 @@ */ package jdk.internal.classfile.impl; -import java.lang.classfile.constantpool.InvokeDynamicEntry; -import java.lang.constant.ClassDesc; -import static java.lang.constant.ConstantDescs.*; -import java.lang.constant.MethodTypeDesc; +import java.lang.classfile.Attribute; +import java.lang.classfile.Attributes; +import java.lang.classfile.BufWriter; import java.lang.classfile.ClassFile; +import java.lang.classfile.Label; +import java.lang.classfile.attribute.StackMapTableAttribute; import java.lang.classfile.constantpool.ClassEntry; import java.lang.classfile.constantpool.ConstantDynamicEntry; -import java.lang.classfile.constantpool.MemberRefEntry; import java.lang.classfile.constantpool.ConstantPoolBuilder; +import java.lang.classfile.constantpool.InvokeDynamicEntry; +import java.lang.classfile.constantpool.MemberRefEntry; +import java.lang.constant.ClassDesc; +import java.lang.constant.MethodTypeDesc; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Arrays; @@ -41,15 +45,10 @@ import java.util.List; import java.util.Objects; import java.util.stream.Collectors; -import java.lang.classfile.Attribute; +import jdk.internal.constant.ReferenceClassDescImpl; import static java.lang.classfile.ClassFile.*; -import static jdk.internal.constant.ConstantUtils.binaryNameToDesc; - -import java.lang.classfile.BufWriter; -import java.lang.classfile.Label; -import java.lang.classfile.attribute.StackMapTableAttribute; -import java.lang.classfile.Attributes; +import static java.lang.constant.ConstantDescs.*; /** * StackMapGenerator is responsible for stack map frames generation. @@ -1249,14 +1248,14 @@ private static record Type(int tag, ClassDesc sym, int bci) { //frequently used types to reduce footprint static final Type OBJECT_TYPE = referenceType(CD_Object), THROWABLE_TYPE = referenceType(CD_Throwable), - INT_ARRAY_TYPE = referenceType(CD_int.arrayType()), - BOOLEAN_ARRAY_TYPE = referenceType(CD_boolean.arrayType()), - BYTE_ARRAY_TYPE = referenceType(CD_byte.arrayType()), - CHAR_ARRAY_TYPE = referenceType(CD_char.arrayType()), - SHORT_ARRAY_TYPE = referenceType(CD_short.arrayType()), - LONG_ARRAY_TYPE = referenceType(CD_long.arrayType()), - DOUBLE_ARRAY_TYPE = referenceType(CD_double.arrayType()), - FLOAT_ARRAY_TYPE = referenceType(CD_float.arrayType()), + INT_ARRAY_TYPE = referenceType(ReferenceClassDescImpl.ofValidated("[I")), + BOOLEAN_ARRAY_TYPE = referenceType(ReferenceClassDescImpl.ofValidated("[Z")), + BYTE_ARRAY_TYPE = referenceType(ReferenceClassDescImpl.ofValidated("[B")), + CHAR_ARRAY_TYPE = referenceType(ReferenceClassDescImpl.ofValidated("[C")), + SHORT_ARRAY_TYPE = referenceType(ReferenceClassDescImpl.ofValidated("[S")), + LONG_ARRAY_TYPE = referenceType(ReferenceClassDescImpl.ofValidated("[J")), + DOUBLE_ARRAY_TYPE = referenceType(ReferenceClassDescImpl.ofValidated("[D")), + FLOAT_ARRAY_TYPE = referenceType(ReferenceClassDescImpl.ofValidated("[F")), STRING_TYPE = referenceType(CD_String), CLASS_TYPE = referenceType(CD_Class), METHOD_HANDLE_TYPE = referenceType(CD_MethodHandle), @@ -1321,8 +1320,8 @@ Type mergeComponentFrom(Type from, ClassHierarchyImpl context) { } } - private static final ClassDesc CD_Cloneable = binaryNameToDesc("java.lang.Cloneable"); - private static final ClassDesc CD_Serializable = binaryNameToDesc("java.io.Serializable"); + private static final ClassDesc CD_Cloneable = ReferenceClassDescImpl.ofValidated("Ljava/lang/Cloneable;"); + private static final ClassDesc CD_Serializable = ReferenceClassDescImpl.ofValidated("Ljava/io/Serializable;"); private Type mergeReferenceFrom(Type from, ClassHierarchyImpl context) { if (from == NULL_TYPE) { diff --git a/src/java.base/share/classes/module-info.java b/src/java.base/share/classes/module-info.java index ca253546e98..ed4bfe0c55b 100644 --- a/src/java.base/share/classes/module-info.java +++ b/src/java.base/share/classes/module-info.java @@ -165,6 +165,7 @@ java.desktop, java.logging, java.management, + java.management.rmi, java.naming, java.rmi, jdk.charsets, diff --git a/src/java.base/share/classes/sun/launcher/resources/launcher_de.properties b/src/java.base/share/classes/sun/launcher/resources/launcher_de.properties index 5da8b4bea5c..12ab942f91f 100644 --- a/src/java.base/share/classes/sun/launcher/resources/launcher_de.properties +++ b/src/java.base/share/classes/sun/launcher/resources/launcher_de.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2007, 2023, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2007, 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 @@ -35,7 +35,7 @@ native Agent Library . Beispiel: -agentlib:jdwp\n sieh # Translators please note do not translate the options themselves java.launcher.X.usage=\n -Xbatch Deaktiviert die Hintergrundkompilierung\n -Xbootclasspath/a:\n An das Ende des Bootstrap Classpaths anhängen\n -Xcheck:jni Führt zusätzliche Prüfungen für JNI-Funktionen aus\n -Xcomp Erzwingt die Kompilierung von Methoden beim ersten Aufruf\n -Xdebug Führt keine Aktion aus. Ist veraltet und wird in einem zukünftigen Release entfernt.\n -Xdiag Zeigt zusätzliche Diagnosemeldungen an\n -Xfuture Aktiviert strengste Prüfungen, als möglicher zukünftiger Standardwert erwartet.\n Diese Option ist veraltet und kann in einem\n zukünftigen Release entfernt werden.\n -Xint Nur Ausführung im interpretierten Modus\n -Xinternalversion\n Zeigt detailliertere JVM-Versionsinformationen an als die\n Option -version\n -Xlog: Konfiguriert oder aktiviert Logging mit dem einheitlichen Java Virtual\n Machine-(JVM-)Logging-Framework. Verwenden Sie -Xlog:help\n für weitere Einzelheiten.\n -Xloggc: Protokolliert den GC-Status in einer Datei mit Zeitstempeln.\n Diese Option ist veraltet und kann in einem\n zukünftigen Release entfernt werden. Wird durch -Xlog:gc: ersetzt.\n -Xmixed Ausführung im gemischten Modus (Standard)\n -Xmn Legt die anfängliche und maximale Größe (in Byte) des Heaps\n für die Young Generation (Nursery) fest\n -Xms Legt die anfängliche Java-Heap-Größe fest\n -Xmx Legt die maximale Java-Heap-Größe fest\n -Xnoclassgc Deaktiviert die Klassen-Garbage Collection\n -Xrs Reduziert die Verwendung von BS-Signalen durch Java/VM (siehe Dokumentation)\n -Xshare:auto Verwendet freigegebene Klassendaten, wenn möglich (Standard)\n -Xshare:off Versucht nicht, freigegebene Klassendaten zu verwenden\n -Xshare:on Erfordert die Verwendung freigegebener Klassendaten, verläuft sonst nicht erfolgreich.\n Diese Testoption kann zeitweise zu\n Fehlern führen. Sie darf nicht in Produktionsumgebungen verwendet werden.\n -XshowSettings Zeigt alle Einstellungen an und fährt fort\n -XshowSettings:all\n Zeigt alle Einstellungen als Verbose-Ausgabe an und fährt fort\n -XshowSettings:locale\n Zeigt alle gebietsschemabezogenen Einstellungen an und fährt fort\n -XshowSettings:properties\n Zeigt alle Eigenschaftseinstellungen an und fährt fort\n -XshowSettings:vm\n Zeigt alle VM-bezogenen Einstellungen an und fährt fort\n -XshowSettings:security\n Zeigt alle Sicherheitseinstellungen an und fährt fort\n -XshowSettings:security:all\n Zeigt alle Sicherheitseinstellungen an und fährt fort\n -XshowSettings:security:properties\n Zeigt Sicherheitseigenschaften an und fährt fort\n -XshowSettings:security:providers\n Zeigt statische Sicherheitsprovidereinstellungen an und fährt fort\n -XshowSettings:security:tls\n Zeigt TLS-bezogene Sicherheitseinstellungen an und fährt fort\n -XshowSettings:system\n (Nur Linux) Zeigt die Konfiguration des Hostsystems oder Containers an\n und fährt fort\n -Xss Legt die Stackgröße des Java-Threads fest\n Die tatsächliche \ -Größe kann auf ein Vielfaches der\n Systemseitengröße aufgerundet werden, wenn für das Betriebssystem erforderlich.\n -Xverify Legt den Modus der Bytecodeverifizierung fest\n Beachten Sie, dass die Option -Xverify:none veraltet ist und\n in einem zukünftigen Release entfernt werden kann.\n --add-reads =(,)*\n Aktualisiert , damit gelesen wird, ungeachtet\n der Moduldeklaration. \n kann ALL-UNNAMED sein, um alle unbenannten\n Module zu lesen.\n --add-exports /=(,)*\n Aktualisiert , um in zu exportieren,\n ungeachtet der Moduldeklaration.\n kann ALL-UNNAMED sein, um in alle\n unbenannten Module zu exportieren.\n --add-opens /=(,)*\n Aktualisiert , um in\n zu öffnen, ungeachtet der Moduldeklaration.\n --limit-modules [,...]\n Grenzt die Gesamtmenge der beobachtbaren Module ein\n --patch-module =({0})*\n Überschreibt oder erweitert ein Modul mit Klassen und Ressourcen\n in JAR-Dateien oder Verzeichnissen.\n --source \n Legt die Version der Quelle im Quelldateimodus fest.\n --finalization=\n Steuert, ob die JVM Objekte finalisiert.\n Dabei ist entweder "enabled" oder "disabled".\n Die Finalisierung ist standardmäßig aktiviert.\n\nDiese zusätzlichen Optionen können jederzeit ohne vorherige Ankündigung geändert werden.\n +Größe kann auf ein Vielfaches der\n Systemseitengröße aufgerundet werden, wenn für das Betriebssystem erforderlich.\n -Xverify Legt den Modus der Bytecodeverifizierung fest\n Beachten Sie, dass die Option -Xverify:none veraltet ist und\n in einem zukünftigen Release entfernt werden kann.\n --add-reads =(,)*\n Aktualisiert , damit gelesen wird, ungeachtet\n der Moduldeklaration. \n kann ALL-UNNAMED sein, um alle unbenannten\n Module zu lesen.\n --add-exports /=(,)*\n Aktualisiert , um in zu exportieren,\n ungeachtet der Moduldeklaration.\n kann ALL-UNNAMED sein, um in alle\n unbenannten Module zu exportieren.\n --add-opens /=(,)*\n Aktualisiert , um in\n zu öffnen, ungeachtet der Moduldeklaration.\n --limit-modules [,...]\n Grenzt die Gesamtmenge der beobachtbaren Module ein\n --patch-module =({0})*\n Überschreibt oder erweitert ein Modul mit Klassen und Ressourcen\n in JAR-Dateien oder Verzeichnissen.\n --source \n Legt die Version der Quelle im Quelldateimodus fest.\n --finalization=\n Steuert, ob die JVM Objekte finalisiert.\n Dabei ist entweder "enabled" oder "disabled".\n Die Finalisierung ist standardmäßig aktiviert.\n --sun-misc-unsafe-memory-access=\n Verwendung der nicht unterstützten API sun.misc.Unsafe zulassen oder verweigern\n ist "allow", "warn", "debug" oder "deny".\n Der Standardwert ist "allow".\n\nDiese zusätzlichen Optionen können jederzeit ohne vorherige Ankündigung geändert werden.\n # Translators please note do not translate the options themselves java.launcher.X.macosx.usage=\nDie folgenden Optionen sind für macOS spezifisch:\n -XstartOnFirstThread\n Führt die main()-Methode für den ersten (AppKit-)Thread aus\n -Xdock:name=\n Setzt den im Dock angezeigten Standardanwendungsnamen außer Kraft\n -Xdock:icon=\n Setzt das im Dock angezeigte Standardsymbol außer Kraft\n\n @@ -52,6 +52,7 @@ java.launcher.jar.error1=Fehler: Beim Versuch, Datei {0} zu öffnen, ist ein une java.launcher.jar.error2=Manifest in {0} nicht gefunden java.launcher.jar.error3=kein Hauptmanifestattribut, in {0} java.launcher.jar.error4=Fehler beim Laden des Java-Agents in {0} +java.launcher.jar.error5=Fehler: Beim Versuch, Datei {0} zu schließen, ist ein unerwarteter Fehler aufgetreten java.launcher.jar.error.illegal.ena.value=Fehler: Ungültiger Wert "{0}" für das Manifestattribut "Enable-Native-Access". Nur ''ALL-UNNAMED'' ist zulässig java.launcher.init.error=Initialisierungsfehler java.launcher.javafx.error1=Fehler: Die JavaFX-Methode launchApplication hat die falsche Signatur, sie\nmuss als statisch deklariert werden und einen Wert vom Typ VOID zurückgeben diff --git a/src/java.base/share/classes/sun/launcher/resources/launcher_ja.properties b/src/java.base/share/classes/sun/launcher/resources/launcher_ja.properties index 4455b2f8020..5d5223d49c6 100644 --- a/src/java.base/share/classes/sun/launcher/resources/launcher_ja.properties +++ b/src/java.base/share/classes/sun/launcher/resources/launcher_ja.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2007, 2023, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2007, 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 @@ -37,7 +37,7 @@ java.launcher.opt.footer = \ -cp <ディレクトリおよびzip/jarファイ # Translators please note do not translate the options themselves java.launcher.X.usage=\n -Xbatch バックグラウンド・コンパイルを無効にします\n -Xbootclasspath/a:\n ブートストラップ・クラス・パスの最後に追加します\n -Xcheck:jni JNI関数に対する追加のチェックを実行します\n -Xcomp 初回呼出し時にメソッドのコンパイルを強制します\n -Xdebug 何も実行されません。将来のリリースで削除されるため、非推奨になりました。\n -Xdiag 追加の診断メッセージを表示します\n -Xfuture 将来のデフォルトを見越して、最も厳密なチェックを有効にします\n このオプションは非推奨であり、将来のリリースで削除される\n 可能性があります。\n -Xint インタプリタ・モードの実行のみ\n -Xinternalversion\n -versionオプションより詳細なJVMバージョン情報を\n 表示します\n -Xlog: Java Virtual Machine (JVM)統合ロギング・フレームワークでの\n ロギングを構成または有効化します。詳細は、-Xlog:helpを\n 使用してください。\n -Xloggc: タイムスタンプが付いたファイルにGCステータスのログを記録します\n このオプションは非推奨であり、将来のリリースで削除される\n 可能性があります。-Xlog:gc:で置換されています。\n -Xmixed 混合モードの実行(デフォルト)\n -Xmn 若い世代(ナーサリ)のヒープの初期サイズおよび最大サイズ\n (バイト単位)を設定します\n -Xms Javaの初期ヒープ・サイズを設定します\n -Xmx Javaの最大ヒープ・サイズを設定します\n -Xnoclassgc クラスのガベージ・コレクションを無効にします\n -Xrs Java/VMによるOSシグナルの使用を削減します(ドキュメントを参照)\n -Xshare:auto 可能であれば共有クラス・データを使用します(デフォルト)\n -Xshare:off \ 共有クラス・データの使用を試みません\n -Xshare:on 共有クラス・データの使用を必須にし、できなければ失敗します。\n これはテスト・オプションであり、断続的な失敗につながる\n 可能性があります。本番環境では使用しないでください。\n -XshowSettings すべての設定を表示して続行します\n -XshowSettings:all\n すべての設定を詳細に表示して続行します\n -XshowSettings:locale\n すべてのロケール関連の設定を表示して続行します\n -XshowSettings:properties\n すべてのプロパティ設定を表示して続行します\n -XshowSettings:vm\n すべてのVM関連の設定を表示して続行します\n -XshowSettings:security\n すべてのセキュリティ設定を表示して続行します\n -XshowSettings:security:all\n すべてのセキュリティ設定を表示して続行します\n -XshowSettings:security:properties\n セキュリティ・プロパティを表示して続行します\n -XshowSettings:security:providers\n 静的セキュリティ・プロバイダ設定を表示して続行します\n -XshowSettings:security:tls\n TLS関連のセキュリティ設定を表示して続行します\n -XshowSettings:system\n (Linuxのみ)ホスト・システムまたはコンテナを表示します\n 構成して続行します\n -Xss javaスレッドのスタック・サイズを設定します\n 実際のサイズは、次の倍数に切り上げられる場合があります: \n オペレーティング・システムの要件に応じたシステム・ページ・サイズ。\n -Xverify バイトコード・ベリファイアのモードを設定します\n オプション-Xverify:noneは非推奨になり、\n 将来のリリースで削除される可能性があります。\n --add-reads =(,)*\n モジュール宣言に関係なく、を更新してを\n \ -読み取ります。 \n をALL-UNNAMEDに設定すると、すべての名前のないモジュールを\n 読み取ることができます。\n --add-exports /=(,)*\n モジュール宣言に関係なく、を更新してに\n エクスポートします。\n をALL-UNNAMEDに設定すると、すべての名前のないモジュールに\n エクスポートできます。\n --add-opens /=(,)*\n モジュール宣言に関係なく、を更新してを\n に開きます。\n --limit-modules [,...]\n 参照可能なモジュールの領域を制限します\n --patch-module =({0})*\n JARファイルまたはディレクトリのクラスおよびリソースで\n モジュールをオーバーライドまたは拡張します。\n --source \n ソースファイル・モードでソースのバージョンを設定します。\n --finalization=\n JVMがオブジェクトのファイナライズを実行するかどうかを制御します\n は"enabled"または"disabled"のいずれかです。\n ファイナライズはデフォルトで有効になっています。\n\nこの追加オプションは予告なしに変更されることがあります。\n +読み取ります。 \n をALL-UNNAMEDに設定すると、すべての名前のないモジュールを\n 読み取ることができます。\n --add-exports /=(,)*\n モジュール宣言に関係なく、を更新してに\n エクスポートします。\n をALL-UNNAMEDに設定すると、すべての名前のないモジュールに\n エクスポートできます。\n --add-opens /=(,)*\n モジュール宣言に関係なく、を更新してを\n に開きます。\n --limit-modules [,...]\n 参照可能なモジュールの領域を制限します\n --patch-module =({0})*\n JARファイルまたはディレクトリのクラスおよびリソースで\n モジュールをオーバーライドまたは拡張します。\n --source \n ソースファイル・モードでソースのバージョンを設定します。\n --finalization=\n JVMがオブジェクトのファイナライズを実行するかどうかを制御します\n は"enabled"または"disabled"のいずれかです。\n ファイナライズはデフォルトで有効になっています。\n --sun-misc-unsafe-memory-access=\n サポートされていないAPI sun.misc.Unsafeの使用を許可または拒否します\n は"allow"、"warn"、"debug"または"deny"のいずれかです。\n デフォルト値は、"allow"です。\n\nこの追加オプションは予告なしに変更されることがあります。\n # Translators please note do not translate the options themselves java.launcher.X.macosx.usage=\n次のオプションはmacOS固有です:\n -XstartOnFirstThread\n main()メソッドを最初(AppKit)のスレッドで実行する\n -Xdock:name=\n Dockに表示されるデフォルト・アプリケーション名をオーバーライドする\n -Xdock:icon=\n Dockに表示されるデフォルト・アイコンをオーバーライドする\n\n @@ -54,6 +54,7 @@ java.launcher.jar.error1=エラー: ファイル{0}を開こうとしている java.launcher.jar.error2={0}にマニフェストが見つかりません java.launcher.jar.error3={0}にメイン・マニフェスト属性がありません java.launcher.jar.error4={0}内のJavaエージェントのロード中にエラーが発生しました +java.launcher.jar.error5=エラー: ファイル{0}を閉じるときに、予期しないエラーが発生しました java.launcher.jar.error.illegal.ena.value=エラー: Enable-Native-Accessマニフェスト属性の値"{0}"が不正です。''ALL-UNNAMED''のみ許可されます java.launcher.init.error=初期化エラー java.launcher.javafx.error1=エラー: JavaFX launchApplicationメソッドに誤ったシグネチャがあり、\nstaticを宣言してvoid型の値を返す必要があります diff --git a/src/java.base/share/classes/sun/launcher/resources/launcher_zh_CN.properties b/src/java.base/share/classes/sun/launcher/resources/launcher_zh_CN.properties index d497402042a..c156e234cef 100644 --- a/src/java.base/share/classes/sun/launcher/resources/launcher_zh_CN.properties +++ b/src/java.base/share/classes/sun/launcher/resources/launcher_zh_CN.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2007, 2023, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2007, 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 @@ -35,7 +35,7 @@ java.launcher.opt.footer = \ -cp <目录和 zip/jar 文件的类搜索路径> # Translators please note do not translate the options themselves java.launcher.X.usage=\n -Xbatch 禁用后台编译\n -Xbootclasspath/a:<以 {0} 分隔的目录和 zip/jar 文件>\n 附加在引导类路径末尾\n -Xcheck:jni 对 JNI 函数执行其他检查\n -Xcomp 强制在首次调用时编译方法\n -Xdebug 不执行任何操作;已过时,将在未来发行版中删除。\n -Xdiag 显示附加诊断消息\n -Xfuture 启用最严格的检查,预期将来的默认值。\n 此选项已过时,可能会在\n 未来发行版中删除。\n -Xint 仅解释模式执行\n -Xinternalversion\n 显示比 -version 选项更详细的\n JVM 版本信息\n -Xlog: 配置或启用采用 Java 虚拟\n 机 (Java Virtual Machine, JVM) 统一记录框架进行事件记录。使用 -Xlog:help\n 可了解详细信息。\n -Xloggc: 将 GC 状态记录在文件中(带时间戳)。\n 此选项已过时,可能会在\n 将来的发行版中删除。它将替换为 -Xlog:gc:。\n -Xmixed 混合模式执行(默认值)\n -Xmn 为年轻代(新生代)设置初始和最大堆大小\n (以字节为单位)\n -Xms 设置初始 Java 堆大小\n -Xmx 设置最大 Java 堆大小\n -Xnoclassgc 禁用类垃圾收集\n -Xrs 减少 Java/VM 对操作系统信号的使用(请参见文档)\n -Xshare:auto 在可能的情况下使用共享类数据(默认值)\n -Xshare:off 不尝试使用共享类数据\n -Xshare:on 要求使用共享类数据,否则将失败。\n 这是一个测试选项,可能导致间歇性\n 故障。不应在生产环境中使用它。\n -XshowSettings 显示所有设置并继续\n -XshowSettings:all\n 详细显示所有设置并继续\n -XshowSettings:locale\n 显示所有与区域设置相关的设置并继续\n -XshowSettings:properties\n 显示所有属性设置并继续\n -XshowSettings:vm\n 显示所有与 vm 相关的设置并继续\n -XshowSettings:security\n 显示所有安全设置并继续\n -XshowSettings:security:all\n 显示所有安全设置并继续\n -XshowSettings:security:properties\n \ - 显示安全属性并继续\n -XshowSettings:security:providers\n 显示静态安全提供方设置并继续\n -XshowSettings:security:tls\n 显示与 TLS 相关的安全设置并继续\n -XshowSettings:system\n (仅 Linux)显示主机系统或容器\n 配置并继续\n -Xss 设置 Java 线程堆栈大小\n 实际大小可以舍入到\n 操作系统要求的系统页面大小的倍数。\n -Xverify 设置字节码验证器的模式\n 请注意,选项 -Xverify:none 已过时,\n 可能会在未来发行版中删除。\n --add-reads =(,)*\n 更新 以读取 ,而无论\n 模块如何声明。 \n 可以是 ALL-UNNAMED,将读取所有未命名\n 模块。\n --add-exports /=(,)*\n 更新 以将 导出到 ,\n 而无论模块如何声明。\n 可以是 ALL-UNNAMED,将导出到所有\n 未命名模块。\n --add-opens /=(,)*\n 更新 以在 中打开\n ,而无论模块如何声明。\n --limit-modules [,...]\n 限制可观察模块的领域\n --patch-module =({0})*\n 使用 JAR 文件或目录中的类和资源\n 覆盖或增强模块。\n --source \n 设置源文件模式中源的版本。\n --finalization=\n 控制 JVM 是否执行对象最终处理,\n 其中 为 "enabled" 或 "disabled" 之一。\n 默认情况下,最终处理处于启用状态。\n\n这些额外选项如有更改, 恕不另行通知。\n + 显示安全属性并继续\n -XshowSettings:security:providers\n 显示静态安全提供方设置并继续\n -XshowSettings:security:tls\n 显示与 TLS 相关的安全设置并继续\n -XshowSettings:system\n (仅 Linux)显示主机系统或容器\n 配置并继续\n -Xss 设置 Java 线程堆栈大小\n 实际大小可以舍入到\n 操作系统要求的系统页面大小的倍数。\n -Xverify 设置字节码验证器的模式\n 请注意,选项 -Xverify:none 已过时,\n 可能会在未来发行版中删除。\n --add-reads =(,)*\n 更新 以读取 ,而无论\n 模块如何声明。 \n 可以是 ALL-UNNAMED,将读取所有未命名\n 模块。\n --add-exports /=(,)*\n 更新 以将 导出到 ,\n 而无论模块如何声明。\n 可以是 ALL-UNNAMED,将导出到所有\n 未命名模块。\n --add-opens /=(,)*\n 更新 以在 中打开\n ,而无论模块如何声明。\n --limit-modules [,...]\n 限制可观察模块的领域\n --patch-module =({0})*\n 使用 JAR 文件或目录中的类和资源\n 覆盖或增强模块。\n --source \n 设置源文件模式中源的版本。\n --finalization=\n 控制 JVM 是否执行对象最终处理,\n 其中 为 "enabled" 或 "disabled" 之一。\n 默认情况下,最终处理处于启用状态。\n --sun-misc-unsafe-memory-access=\n 允许或拒绝使用不受支持的 API sun.misc.Unsafe\n 为 "allow"、"warn"、"debug" 或 "deny" 之一。\n 默认值为 "allow"。\n\n这些额外选项如有更改, 恕不另行通知。\n # Translators please note do not translate the options themselves java.launcher.X.macosx.usage=\n以下选项是特定于 macOS 的选项:\n -XstartOnFirstThread\n 在第一个 (AppKit) 线程上运行 main() 方法\n -Xdock:name=\n 覆盖停靠栏中显示的默认应用程序名称\n -Xdock:icon=\n 覆盖停靠栏中显示的默认图标\n\n @@ -52,6 +52,7 @@ java.launcher.jar.error1=错误: 尝试打开文件{0}时出现意外错误 java.launcher.jar.error2=在{0}中找不到清单 java.launcher.jar.error3={0}中没有主清单属性 java.launcher.jar.error4=在 {0} 中加载 Java 代理时出错 +java.launcher.jar.error5=错误:尝试关闭文件 {0} 时出现意外错误 java.launcher.jar.error.illegal.ena.value=错误:Enable-Native-Access 清单属性的值 "{0}" 非法。仅允许使用 ''ALL-UNNAMED'' java.launcher.init.error=初始化错误 java.launcher.javafx.error1=错误: JavaFX launchApplication 方法具有错误的签名, 必须\n将方法声明为静态方法并返回空类型的值 diff --git a/src/java.base/share/classes/sun/security/ssl/ClientHello.java b/src/java.base/share/classes/sun/security/ssl/ClientHello.java index 091bfa8986e..babf2bb452d 100644 --- a/src/java.base/share/classes/sun/security/ssl/ClientHello.java +++ b/src/java.base/share/classes/sun/security/ssl/ClientHello.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2022, 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 @@ -903,8 +903,8 @@ private ProtocolVersion negotiateProtocol( throw context.conContext.fatal(Alert.PROTOCOL_VERSION, "The client supported protocol versions " + Arrays.toString( ProtocolVersion.toStringArray(clientSupportedVersions)) + - " are not accepted by server preferences " + - context.activeProtocols); + " are not accepted by server preferences " + Arrays.toString( + ProtocolVersion.toStringArray(context.activeProtocols))); } } diff --git a/src/java.base/share/native/libjli/args.c b/src/java.base/share/native/libjli/args.c index 379291fe8ec..1e9b48730a9 100644 --- a/src/java.base/share/native/libjli/args.c +++ b/src/java.base/share/native/libjli/args.c @@ -501,7 +501,7 @@ static jboolean expand(JLI_List args, const char *str, const char *var_name) { // This is retained until the process terminates as it is saved as the args p = JLI_MemAlloc(JLI_StrLen(str) + 1); while (*str != '\0') { - while (*str != '\0' && isspace(*str)) { + while (*str != '\0' && isspace((unsigned char) *str)) { str++; } @@ -511,7 +511,7 @@ static jboolean expand(JLI_List args, const char *str, const char *var_name) { } arg = p; - while (*str != '\0' && !isspace(*str)) { + while (*str != '\0' && !isspace((unsigned char) *str)) { if (inEnvVar && (*str == '"' || *str == '\'')) { quote = *str++; while (*str != quote && *str != '\0') { @@ -577,7 +577,7 @@ static jboolean expand(JLI_List args, const char *str, const char *var_name) { exit(1); } - assert (*str == '\0' || isspace(*str)); + assert (*str == '\0' || isspace((unsigned char) *str)); } return JNI_TRUE; diff --git a/src/java.base/share/native/libjli/java.c b/src/java.base/share/native/libjli/java.c index be868d011b5..121fa20b358 100644 --- a/src/java.base/share/native/libjli/java.c +++ b/src/java.base/share/native/libjli/java.c @@ -387,73 +387,92 @@ JLI_Launch(int argc, char ** argv, /* main argc, argv */ } \ } while (JNI_FALSE) -#define CHECK_EXCEPTION_NULL_FAIL(obj) \ - do { \ - if ((*env)->ExceptionOccurred(env)) { \ - return 0; \ - } else if (obj == NULL) { \ - return 0; \ - } \ - } while (JNI_FALSE) - /* - * Invoke a static main with arguments. Returns 1 (true) if successful otherwise - * processes the pending exception from GetStaticMethodID and returns 0 (false). + * Invokes static main(String[]) method if found. + * Returns 0 with a pending exception if not found. Returns 1 if invoked, maybe + * a pending exception if the method threw. */ int invokeStaticMainWithArgs(JNIEnv *env, jclass mainClass, jobjectArray mainArgs) { jmethodID mainID = (*env)->GetStaticMethodID(env, mainClass, "main", "([Ljava/lang/String;)V"); - CHECK_EXCEPTION_NULL_FAIL(mainID); + if (mainID == NULL) { + // static main(String[]) not found + return 0; + } (*env)->CallStaticVoidMethod(env, mainClass, mainID, mainArgs); - return 1; + return 1; // method was invoked } /* - * Invoke an instance main with arguments. Returns 1 (true) if successful otherwise - * processes the pending exception from GetMethodID and returns 0 (false). + * Invokes instance main(String[]) method if found. + * Returns 0 with a pending exception if not found. Returns 1 if invoked, maybe + * a pending exception if the method threw. */ int invokeInstanceMainWithArgs(JNIEnv *env, jclass mainClass, jobjectArray mainArgs) { jmethodID constructor = (*env)->GetMethodID(env, mainClass, "", "()V"); - CHECK_EXCEPTION_NULL_FAIL(constructor); + if (constructor == NULL) { + // main class' no-arg constructor not found + return 0; + } jobject mainObject = (*env)->NewObject(env, mainClass, constructor); - CHECK_EXCEPTION_NULL_FAIL(mainObject); + if (mainObject == NULL) { + // main class instance couldn't be constructed + return 0; + } jmethodID mainID = (*env)->GetMethodID(env, mainClass, "main", "([Ljava/lang/String;)V"); - CHECK_EXCEPTION_NULL_FAIL(mainID); + if (mainID == NULL) { + // instance method main(String[]) method not found + return 0; + } (*env)->CallVoidMethod(env, mainObject, mainID, mainArgs); - return 1; + return 1; // method was invoked } /* - * Invoke a static main without arguments. Returns 1 (true) if successful otherwise - * processes the pending exception from GetStaticMethodID and returns 0 (false). + * Invokes no-arg static main() method if found. + * Returns 0 with a pending exception if not found. Returns 1 if invoked, maybe + * a pending exception if the method threw. */ int invokeStaticMainWithoutArgs(JNIEnv *env, jclass mainClass) { jmethodID mainID = (*env)->GetStaticMethodID(env, mainClass, "main", "()V"); - CHECK_EXCEPTION_NULL_FAIL(mainID); + if (mainID == NULL) { + // static main() method couldn't be located + return 0; + } (*env)->CallStaticVoidMethod(env, mainClass, mainID); - return 1; + return 1; // method was invoked } /* - * Invoke an instance main without arguments. Returns 1 (true) if successful otherwise - * processes the pending exception from GetMethodID and returns 0 (false). + * Invokes no-arg instance main() method if found. + * Returns 0 with a pending exception if not found. Returns 1 if invoked, maybe + * a pending exception if the method threw. */ int invokeInstanceMainWithoutArgs(JNIEnv *env, jclass mainClass) { jmethodID constructor = (*env)->GetMethodID(env, mainClass, "", "()V"); - CHECK_EXCEPTION_NULL_FAIL(constructor); + if (constructor == NULL) { + // main class' no-arg constructor not found + return 0; + } jobject mainObject = (*env)->NewObject(env, mainClass, constructor); - CHECK_EXCEPTION_NULL_FAIL(mainObject); + if (mainObject == NULL) { + // couldn't create instance of main class + return 0; + } jmethodID mainID = (*env)->GetMethodID(env, mainClass, "main", "()V"); - CHECK_EXCEPTION_NULL_FAIL(mainID); + if (mainID == NULL) { + // instance method main() not found + return 0; + } (*env)->CallVoidMethod(env, mainObject, mainID); - return 1; + return 1; // method was invoked } int @@ -639,6 +658,8 @@ JavaMain(void* _args) } } if (!ret) { + // An appropriate main method couldn't be located, check and report + // any exception and LEAVE() CHECK_EXCEPTION_LEAVE(1); } @@ -646,8 +667,15 @@ JavaMain(void* _args) * The launcher's exit code (in the absence of calls to * System.exit) will be non-zero if main threw an exception. */ - ret = (*env)->ExceptionOccurred(env) == NULL ? 0 : 1; - + if (ret && (*env)->ExceptionOccurred(env) == NULL) { + // main method was invoked and no exception was thrown from it, + // return success. + ret = 0; + } else { + // Either the main method couldn't be located or an exception occurred + // in the invoked main method, return failure. + ret = 1; + } LEAVE(); } diff --git a/src/java.desktop/macosx/native/libjawt/jawt.m b/src/java.desktop/macosx/native/libjawt/jawt.m index f5dc048a482..36d4e897f60 100644 --- a/src/java.desktop/macosx/native/libjawt/jawt.m +++ b/src/java.desktop/macosx/native/libjawt/jawt.m @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2016, 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 @@ -55,15 +55,14 @@ awt->GetDrawingSurface = awt_GetDrawingSurface; awt->FreeDrawingSurface = awt_FreeDrawingSurface; - if (awt->version >= JAWT_VERSION_1_4) { - awt->Lock = awt_Lock; - awt->Unlock = awt_Unlock; - awt->GetComponent = awt_GetComponent; - if (awt->version >= JAWT_VERSION_9) { - awt->CreateEmbeddedFrame = awt_CreateEmbeddedFrame; - awt->SetBounds = awt_SetBounds; - awt->SynthesizeWindowActivation = awt_SynthesizeWindowActivation; - } + + awt->Lock = awt_Lock; + awt->Unlock = awt_Unlock; + awt->GetComponent = awt_GetComponent; + if (awt->version >= JAWT_VERSION_9) { + awt->CreateEmbeddedFrame = awt_CreateEmbeddedFrame; + awt->SetBounds = awt_SetBounds; + awt->SynthesizeWindowActivation = awt_SynthesizeWindowActivation; } return JNI_TRUE; diff --git a/src/java.desktop/share/classes/java/awt/image/BufferedImage.java b/src/java.desktop/share/classes/java/awt/image/BufferedImage.java index 43f0959158d..cfbebd68710 100644 --- a/src/java.desktop/share/classes/java/awt/image/BufferedImage.java +++ b/src/java.desktop/share/classes/java/awt/image/BufferedImage.java @@ -899,7 +899,7 @@ public WritableRaster getAlphaRaster() { * *

* - * An {@code ArrayOutOfBoundsException} may be thrown + * An {@code ArrayIndexOutOfBoundsException} may be thrown * if the coordinates are not in bounds. * However, explicit bounds checking is not guaranteed. * @@ -933,7 +933,7 @@ public int getRGB(int x, int y) { * *

* - * An {@code ArrayOutOfBoundsException} may be thrown + * An {@code ArrayIndexOutOfBoundsException} may be thrown * if the region is not in bounds. * However, explicit bounds checking is not guaranteed. * @@ -1003,7 +1003,7 @@ public int[] getRGB(int startX, int startY, int w, int h, * *

* - * An {@code ArrayOutOfBoundsException} may be thrown + * An {@code ArrayIndexOutOfBoundsException} may be thrown * if the coordinates are not in bounds. * However, explicit bounds checking is not guaranteed. * @@ -1033,7 +1033,7 @@ public void setRGB(int x, int y, int rgb) { * *

* - * An {@code ArrayOutOfBoundsException} may be thrown + * An {@code ArrayIndexOutOfBoundsException} may be thrown * if the region is not in bounds. * However, explicit bounds checking is not guaranteed. * diff --git a/src/java.desktop/share/classes/javax/print/attribute/standard/OutputBin.java b/src/java.desktop/share/classes/javax/print/attribute/standard/OutputBin.java index 3f817fe6c9b..af6113ad638 100644 --- a/src/java.desktop/share/classes/javax/print/attribute/standard/OutputBin.java +++ b/src/java.desktop/share/classes/javax/print/attribute/standard/OutputBin.java @@ -49,6 +49,8 @@ * IPP attribute name. The enumeration's integer value is the IPP enum value. * The {@code toString()} method returns the IPP string representation of the * attribute value. + * + * @since 23 */ public sealed class OutputBin extends EnumSyntax implements PrintRequestAttribute, PrintJobAttribute permits CustomOutputBin { diff --git a/src/java.desktop/share/classes/javax/swing/table/JTableHeader.java b/src/java.desktop/share/classes/javax/swing/table/JTableHeader.java index 6424846d3d0..6372d6bc4ab 100644 --- a/src/java.desktop/share/classes/javax/swing/table/JTableHeader.java +++ b/src/java.desktop/share/classes/javax/swing/table/JTableHeader.java @@ -951,7 +951,7 @@ public AccessibleContext getAccessibleContext() { private AccessibleContext getCurrentAccessibleContext() { TableColumnModel tcm = table.getColumnModel(); if (tcm != null) { - // Fixes 4772355 - ArrayOutOfBoundsException in + // Fixes 4772355 - ArrayIndexOutOfBoundsException in // JTableHeader if (column < 0 || column >= tcm.getColumnCount()) { return null; @@ -979,7 +979,7 @@ private AccessibleContext getCurrentAccessibleContext() { private Component getCurrentComponent() { TableColumnModel tcm = table.getColumnModel(); if (tcm != null) { - // Fixes 4772355 - ArrayOutOfBoundsException in + // Fixes 4772355 - ArrayIndexOutOfBoundsException in // JTableHeader if (column < 0 || column >= tcm.getColumnCount()) { return null; diff --git a/src/java.desktop/share/classes/sun/font/Type1Font.java b/src/java.desktop/share/classes/sun/font/Type1Font.java index 1cd046eadd1..cc36c193de0 100644 --- a/src/java.desktop/share/classes/sun/font/Type1Font.java +++ b/src/java.desktop/share/classes/sun/font/Type1Font.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 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 @@ -187,7 +187,9 @@ public Type1Font(String platname, Object nativeNames, boolean createdCopy) private synchronized ByteBuffer getBuffer() throws FontFormatException { ByteBuffer bbuf = bufferRef.get(); if (bbuf == null) { - //System.out.println("open T1 " + platName); + if (FontUtilities.isLogging()) { + FontUtilities.logInfo("open Type 1 font: " + platName); + } try { @SuppressWarnings("removal") RandomAccessFile raf = (RandomAccessFile) @@ -229,6 +231,9 @@ protected void close() { void readFile(ByteBuffer buffer) { RandomAccessFile raf = null; FileChannel fc; + if (FontUtilities.isLogging()) { + FontUtilities.logInfo("open Type 1 font: " + platName); + } try { raf = (RandomAccessFile) java.security.AccessController.doPrivileged( diff --git a/src/java.desktop/share/classes/sun/print/resources/serviceui_zh_CN.properties b/src/java.desktop/share/classes/sun/print/resources/serviceui_zh_CN.properties index f4a1982f056..cdc2bb5541f 100644 --- a/src/java.desktop/share/classes/sun/print/resources/serviceui_zh_CN.properties +++ b/src/java.desktop/share/classes/sun/print/resources/serviceui_zh_CN.properties @@ -29,7 +29,7 @@ border.chromaticity=颜色外观 border.copies=份数 border.jobattributes=作业属性 border.media=介质 -border.output=出纸 +border.output=输出 border.orientation=方向 border.printrange=打印区域 border.printservice=打印服务 @@ -63,7 +63,7 @@ label.pstype=类型: label.rangeto=至 label.size=大小(&Z): label.source=来源(&C): -label.outputbins=出纸托盘(&P): +label.outputbins=输出托盘(&P): label.status=状态: label.username=用户名(&U): label.millimetres=(毫米) diff --git a/src/java.desktop/share/native/libmlib_image/mlib_ImageScanPoly.c b/src/java.desktop/share/native/libmlib_image/mlib_ImageScanPoly.c index a643b9c111c..a6f4cfdd36e 100644 --- a/src/java.desktop/share/native/libmlib_image/mlib_ImageScanPoly.c +++ b/src/java.desktop/share/native/libmlib_image/mlib_ImageScanPoly.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2020, 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 @@ -289,13 +289,15 @@ mlib_status mlib_AffineEdges(mlib_affine_param *param, mlib_d64 dX1 = coords[(topIdx - i) & 0x3][0]; mlib_d64 dY2 = coords[(topIdx - i - 1) & 0x3][1]; mlib_d64 dX2 = coords[(topIdx - i - 1) & 0x3][0]; - mlib_d64 x = dX1, slope = (dX2 - dX1) / (dY2 - dY1); + mlib_d64 x = dX1, slope; mlib_s32 y1; mlib_s32 y2; if (dY1 == dY2) continue; + slope = (dX2 - dX1) / (dY2 - dY1); + if (!(IS_FINITE(slope))) { continue; } @@ -330,13 +332,15 @@ mlib_status mlib_AffineEdges(mlib_affine_param *param, mlib_d64 dX1 = coords[(topIdx + i) & 0x3][0]; mlib_d64 dY2 = coords[(topIdx + i + 1) & 0x3][1]; mlib_d64 dX2 = coords[(topIdx + i + 1) & 0x3][0]; - mlib_d64 x = dX1, slope = (dX2 - dX1) / (dY2 - dY1); + mlib_d64 x = dX1, slope; mlib_s32 y1; mlib_s32 y2; if (dY1 == dY2) continue; + slope = (dX2 - dX1) / (dY2 - dY1); + if (!(IS_FINITE(slope))) { continue; } diff --git a/src/java.management.rmi/share/classes/javax/management/remote/rmi/RMIConnectionImpl.java b/src/java.management.rmi/share/classes/javax/management/remote/rmi/RMIConnectionImpl.java index 289bc8f3822..047863d34ef 100644 --- a/src/java.management.rmi/share/classes/javax/management/remote/rmi/RMIConnectionImpl.java +++ b/src/java.management.rmi/share/classes/javax/management/remote/rmi/RMIConnectionImpl.java @@ -46,6 +46,7 @@ import javax.management.remote.JMXServerErrorException; import javax.management.remote.NotificationResult; import javax.security.auth.Subject; +import jdk.internal.access.SharedSecrets; import sun.reflect.misc.ReflectUtil; import static javax.management.remote.rmi.RMIConnector.Util.cast; @@ -108,14 +109,19 @@ public RMIConnectionImpl(RMIServerImpl rmiServer, this.rmiServer = rmiServer; this.connectionId = connectionId; this.defaultClassLoader = defaultClassLoader; - this.subject = subject; + if (subject == null) { this.acc = null; } else { // An authenticated Subject was provided. // Subject Delegation has been removed. - this.acc = JMXSubjectDomainCombiner.getContext(subject); + if (SharedSecrets.getJavaLangAccess().allowSecurityManager()) { + // SM is allowed. Will use ACC created with Subject: + this.acc = JMXSubjectDomainCombiner.getContext(subject); + } else { + this.acc = null; + } } this.mbeanServer = rmiServer.getMBeanServer(); @@ -1292,10 +1298,21 @@ public NotificationResult run() { return getServerNotifFwd().fetchNotifs(csn, t, mn); } }; - if (acc == null) - return action.run(); - else - return AccessController.doPrivileged(action, acc); + if (!SharedSecrets.getJavaLangAccess().allowSecurityManager()) { + // Modern case + if (subject == null) { + return action.run(); + } else { + return Subject.doAs(subject, action); + } + } else { + // SM permitted + if (acc == null) { + return action.run(); // No Subject or ACC + } else { + return AccessController.doPrivileged(action, acc); + } + } } finally { serverCommunicatorAdmin.rspOutgoing(); } @@ -1411,16 +1428,36 @@ private Object doPrivilegedOperation(final int operation, serverCommunicatorAdmin.reqIncoming(); try { PrivilegedOperation op = new PrivilegedOperation(operation, params); - if (acc == null) { - try { - return op.run(); - } catch (Exception e) { - if (e instanceof RuntimeException) - throw (RuntimeException) e; - throw new PrivilegedActionException(e); + if (!SharedSecrets.getJavaLangAccess().allowSecurityManager()) { + // Modern case + if (subject == null) { + try { + return op.run(); + } catch (Exception e) { + if (e instanceof RuntimeException) { + throw (RuntimeException) e; + } else { + throw new PrivilegedActionException(e); + } + } + } else { + return Subject.doAs(subject, op); } } else { - return AccessController.doPrivileged(op, acc); + // SM permitted + if (acc == null) { + try { + return op.run(); + } catch (Exception e) { + if (e instanceof RuntimeException) { + throw (RuntimeException) e; + } else { + throw new PrivilegedActionException(e); + } + } + } else { + return AccessController.doPrivileged(op, acc); + } } } catch (Error e) { throw new JMXServerErrorException(e.toString(),e); @@ -1585,15 +1622,25 @@ private T unwrap(final MarshalledObject mo, } try { final ClassLoader old = AccessController.doPrivileged(new SetCcl(cl)); - try{ - if (acc != null) { - return AccessController.doPrivileged( - (PrivilegedExceptionAction) () -> - wrappedClass.cast(mo.get()), acc); - }else{ - return wrappedClass.cast(mo.get()); + try { + if (!SharedSecrets.getJavaLangAccess().allowSecurityManager()) { + // Modern case + if (subject != null) { + return Subject.doAs(subject, (PrivilegedExceptionAction) () -> wrappedClass.cast(mo.get())); + } else { + return wrappedClass.cast(mo.get()); + } + } else { + // SM permitted + if (acc != null) { + return AccessController.doPrivileged( + (PrivilegedExceptionAction) () -> + wrappedClass.cast(mo.get()), acc); + } else { + return wrappedClass.cast(mo.get()); + } } - }finally{ + } finally { AccessController.doPrivileged(new SetCcl(old)); } } catch (PrivilegedActionException pe) { diff --git a/src/java.management/share/classes/com/sun/jmx/remote/internal/ServerNotifForwarder.java b/src/java.management/share/classes/com/sun/jmx/remote/internal/ServerNotifForwarder.java index e04a375c998..7ea1e485869 100644 --- a/src/java.management/share/classes/com/sun/jmx/remote/internal/ServerNotifForwarder.java +++ b/src/java.management/share/classes/com/sun/jmx/remote/internal/ServerNotifForwarder.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2002, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2002, 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 @@ -343,10 +343,9 @@ public void terminate() { //---------------- // PRIVATE METHODS //---------------- - @SuppressWarnings("removal") private Subject getSubject() { - return Subject.getSubject(AccessController.getContext()); + return Subject.current(); } private void checkState() throws IOException { diff --git a/src/java.management/share/classes/com/sun/jmx/remote/security/MBeanServerFileAccessController.java b/src/java.management/share/classes/com/sun/jmx/remote/security/MBeanServerFileAccessController.java index d694d9cce31..bc14b6ad3f2 100644 --- a/src/java.management/share/classes/com/sun/jmx/remote/security/MBeanServerFileAccessController.java +++ b/src/java.management/share/classes/com/sun/jmx/remote/security/MBeanServerFileAccessController.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 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 @@ -42,6 +42,7 @@ import javax.management.MBeanServer; import javax.management.ObjectName; import javax.security.auth.Subject; +import jdk.internal.access.SharedSecrets; /** *

An object of this class implements the MBeanServerAccessController @@ -300,16 +301,19 @@ private static Properties propertiesFromFile(String fname) } } + @SuppressWarnings("removal") private synchronized void checkAccess(AccessType requiredAccess, String arg) { - @SuppressWarnings("removal") - final AccessControlContext acc = AccessController.getContext(); - @SuppressWarnings("removal") - final Subject s = - AccessController.doPrivileged(new PrivilegedAction<>() { - public Subject run() { - return Subject.getSubject(acc); - } + Subject s = null; + if (!SharedSecrets.getJavaLangAccess().allowSecurityManager()) { + s = Subject.current(); + } else { + final AccessControlContext acc = AccessController.getContext(); + s = AccessController.doPrivileged(new PrivilegedAction<>() { + public Subject run() { + return Subject.getSubject(acc); + } }); + } if (s == null) return; /* security has not been enabled */ final Set principals = s.getPrincipals(); String newPropertyValue = null; diff --git a/src/java.management/share/classes/javax/management/monitor/Monitor.java b/src/java.management/share/classes/javax/management/monitor/Monitor.java index aa6ec14ab63..2ccb96b8db6 100644 --- a/src/java.management/share/classes/javax/management/monitor/Monitor.java +++ b/src/java.management/share/classes/javax/management/monitor/Monitor.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999, 2022, 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 @@ -60,6 +60,8 @@ import javax.management.NotificationBroadcasterSupport; import javax.management.ObjectName; import javax.management.ReflectionException; +import javax.security.auth.Subject; +import jdk.internal.access.SharedSecrets; import static javax.management.monitor.MonitorNotification.*; /** @@ -169,8 +171,9 @@ public final synchronized void setDerivedGaugeTimeStamp( new CopyOnWriteArrayList<>(); /** - * AccessControlContext of the Monitor.start() caller. + * Subject and possibly AccessControlContext of the Monitor.start() caller. */ + private volatile Subject subject; @SuppressWarnings("removal") private static final AccessControlContext noPermissionsACC = new AccessControlContext( @@ -713,10 +716,14 @@ void doStart() { // cleanupIsComplexTypeAttribute(); - // Cache the AccessControlContext of the Monitor.start() caller. + // Cache the Subject or AccessControlContext of the Monitor.start() caller. // The monitor tasks will be executed within this context. // - acc = AccessController.getContext(); + if (!SharedSecrets.getJavaLangAccess().allowSecurityManager()) { + subject = Subject.current(); + } else { + acc = AccessController.getContext(); + } // Start the scheduler. // @@ -747,8 +754,9 @@ void doStop() { // cleanupFutures(); - // Reset the AccessControlContext. + // Reset the Subject and AccessControlContext. // + subject = null; acc = noPermissionsACC; // Reset the complex type attribute information @@ -1512,9 +1520,11 @@ public Future submit() { @SuppressWarnings("removal") public void run() { final ScheduledFuture sf; + final Subject s; final AccessControlContext ac; synchronized (Monitor.this) { sf = Monitor.this.schedulerFuture; + s = Monitor.this.subject; ac = Monitor.this.acc; } PrivilegedAction action = new PrivilegedAction<>() { @@ -1531,10 +1541,20 @@ public Void run() { return null; } }; - if (ac == null) { - throw new SecurityException("AccessControlContext cannot be null"); + if (!SharedSecrets.getJavaLangAccess().allowSecurityManager()) { + // No SecurityManager permitted: + if (s == null) { + action.run(); + } else { + Subject.doAs(s, action); + } + } else { + if (ac == null) { + throw new SecurityException("AccessControlContext cannot be null"); + } + // ACC means SM is permitted. + AccessController.doPrivileged(action, ac); } - AccessController.doPrivileged(action, ac); synchronized (Monitor.this) { if (Monitor.this.isActive() && Monitor.this.schedulerFuture == sf) { diff --git a/src/java.xml/share/classes/com/sun/org/apache/xerces/internal/impl/msg/XMLMessages_de.properties b/src/java.xml/share/classes/com/sun/org/apache/xerces/internal/impl/msg/XMLMessages_de.properties index 9227a61a608..9f24c42b110 100644 --- a/src/java.xml/share/classes/com/sun/org/apache/xerces/internal/impl/msg/XMLMessages_de.properties +++ b/src/java.xml/share/classes/com/sun/org/apache/xerces/internal/impl/msg/XMLMessages_de.properties @@ -275,7 +275,7 @@ RootElementTypeMustMatchDoctypedecl = Document Root-Element "{1}"muss mit DOCTYPE-Root "{0}" übereinstimmen. UndeclaredElementInContentSpec = Contentmodell des Elements "{0}" verweist auf das nicht deklarierte Element "{1}". UniqueNotationName = Deklaration für die Notation "{0}" ist nicht eindeutig. Ein jeweiliger Name darf nicht in mehreren Notationsdeklarationen deklariert werden. - ENTITYFailedInitializeGrammar = ENTITYDatatype-Validator: Nicht erfolgreich. Initialisierungsmethode muss mit einer gültigen Grammatikreferenz aufgerufen werden. \t + ENTITYFailedInitializeGrammar = ENTITYDatatype-Validator: Nicht erfolgreich. Initialisierungsmethode muss mit einer gültigen Grammatikreferenz aufgerufen werden. ENTITYNotUnparsed = ENTITY "{0}" ist geparst. ENTITYNotValid = ENTITY "{0}" ist nicht gültig. EmptyList = Werte der Typen ENTITIES, IDREFS und NMTOKENS dürfen keine leeren Listen sein. diff --git a/src/java.xml/share/classes/com/sun/org/apache/xerces/internal/impl/msg/XMLMessages_ja.properties b/src/java.xml/share/classes/com/sun/org/apache/xerces/internal/impl/msg/XMLMessages_ja.properties index ef0edf1b188..00bced117f6 100644 --- a/src/java.xml/share/classes/com/sun/org/apache/xerces/internal/impl/msg/XMLMessages_ja.properties +++ b/src/java.xml/share/classes/com/sun/org/apache/xerces/internal/impl/msg/XMLMessages_ja.properties @@ -275,7 +275,7 @@ RootElementTypeMustMatchDoctypedecl = ドキュメント・ルート要素"{1}"はDOCTYPEルート"{0}"と一致する必要があります。 UndeclaredElementInContentSpec = 要素"{0}"のコンテンツ・モデルで未宣言の要素"{1}"が参照されています。 UniqueNotationName = 表記法"{0}"の宣言が一意ではありません。同じ名前を複数の表記法宣言で宣言しないでください。 - ENTITYFailedInitializeGrammar = ENTITYDatatypeバリデータ: 有効な構文参照による初期化メソッドの呼出しに失敗しました。 \t + ENTITYFailedInitializeGrammar = ENTITYDatatypeバリデータ: 有効な構文参照による初期化メソッドの呼出しに失敗しました。 ENTITYNotUnparsed = ENTITY "{0}"は未解析ではありません。 ENTITYNotValid = ENTITY "{0}"は有効ではありません。 EmptyList = タイプENTITIES、IDREFSおよびNMTOKENSの値は空のリストにできません。 diff --git a/src/java.xml/share/classes/com/sun/org/apache/xerces/internal/impl/msg/XMLMessages_zh_CN.properties b/src/java.xml/share/classes/com/sun/org/apache/xerces/internal/impl/msg/XMLMessages_zh_CN.properties index 0b937ce7623..ffe36a06f35 100644 --- a/src/java.xml/share/classes/com/sun/org/apache/xerces/internal/impl/msg/XMLMessages_zh_CN.properties +++ b/src/java.xml/share/classes/com/sun/org/apache/xerces/internal/impl/msg/XMLMessages_zh_CN.properties @@ -275,7 +275,7 @@ RootElementTypeMustMatchDoctypedecl = 文档根元素 "{1}" 必须匹配 DOCTYPE 根 "{0}"。 UndeclaredElementInContentSpec = 元素 "{0}" 的内容模型引用未声明的元素 "{1}"。 UniqueNotationName = 记号 "{0}" 的声明不是唯一的。不能在多个记号声明中声明指定的名称。 - ENTITYFailedInitializeGrammar = ENTITYDatatype 验证程序: 未能使用有效的语法引用调用初始化方法。\t + ENTITYFailedInitializeGrammar = ENTITYDatatype 验证程序: 未能使用有效的语法引用调用初始化方法。 ENTITYNotUnparsed = ENTITY "{0}" 不是未解析的。 ENTITYNotValid = ENTITY "{0}" 无效。 EmptyList = 类型为 ENTITIES, IDREFS 和 NMTOKENS 的值不能是空列表。 diff --git a/src/java.xml/share/classes/com/sun/org/apache/xerces/internal/impl/msg/XPointerMessages_de.properties b/src/java.xml/share/classes/com/sun/org/apache/xerces/internal/impl/msg/XPointerMessages_de.properties index fcd8b9e5a0c..e3f064694d8 100644 --- a/src/java.xml/share/classes/com/sun/org/apache/xerces/internal/impl/msg/XPointerMessages_de.properties +++ b/src/java.xml/share/classes/com/sun/org/apache/xerces/internal/impl/msg/XPointerMessages_de.properties @@ -27,7 +27,7 @@ FormatFailed = Beim Formatieren der folgenden Meldung ist ein interner Fehler au XPointerProcessingError = XPointerProcessingError: Beim Verarbeiten des XPointer-Ausdrucks ist ein Fehler aufgetreten. InvalidXPointerToken = InvalidXPointerToken: XPointer-Ausdruck enthält das ungültige Token "{0}" InvalidXPointerExpression = InvalidXPointerExpression: XPointer-Ausdruck "{0}" ist ungültig. -MultipleShortHandPointers = MultipleShortHandPointers: XPointer-Ausdruck "{0}" ist ungültig. Mehrere ShortHand-Zeiger vorhanden. +MultipleShortHandPointers = MultipleShortHandPointers: Der XPointer-Ausdruck "{0}" ist ungültig. Er enthält mehrere ShortHand-Zeiger. SchemeDataNotFollowedByCloseParenthesis = SchemeDataNotFollowedByCloseParenthesis: XPointer-Ausdruck "{0}" ist ungültig. Auf SchemeData folgte kein ")"-Zeichen. SchemeUnsupported = SchemeUnsupported: XPointer-Schema "{0}" wird nicht unterstützt. InvalidShortHandPointer = InvalidShortHandPointer: NCName von ShortHand-Zeiger "{0}" ist ungültig. diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java index 533541bd108..08c37fc5aa4 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java @@ -950,7 +950,6 @@ public void visitClassDef(JCClassDecl tree) { Optional.ofNullable(env.info.attributionMode.isSpeculative ? argumentAttr.withLocalCacheContext() : null); boolean ctorProloguePrev = env.info.ctorPrologue; - env.info.ctorPrologue = false; try { // Local and anonymous classes have not been entered yet, so we need to // do it now. @@ -995,7 +994,7 @@ public void visitMethodDef(JCMethodDecl tree) { Lint lint = env.info.lint.augment(m); Lint prevLint = chk.setLint(lint); boolean ctorProloguePrev = env.info.ctorPrologue; - env.info.ctorPrologue = false; + Assert.check(!env.info.ctorPrologue); MethodSymbol prevMethod = chk.setMethod(m); try { deferredLintHandler.flush(tree.pos(), lint); diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Enter.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Enter.java index 7478ef1c907..926be3b6e26 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Enter.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Enter.java @@ -209,6 +209,7 @@ public Env classEnv(JCClassDecl tree, Env env) { localEnv.info.lint = null; // leave this to be filled in by Attr, // when annotations have been processed localEnv.info.isAnonymousDiamond = TreeInfo.isDiamond(env.tree); + localEnv.info.ctorPrologue = false; return localEnv; } diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/LambdaToMethod.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/LambdaToMethod.java index 5b70a376f2a..7e5dcee0e8d 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/LambdaToMethod.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/LambdaToMethod.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 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 @@ -1239,7 +1239,7 @@ class LambdaAnalyzerPreprocessor extends TreeTranslator { private int lambdaCount = 0; /** - * List of types undergoing construction via explicit constructor chaining. + * List of types undergoing construction, i.e., in an early construction context. */ private List typesUnderConstruction; @@ -1280,15 +1280,10 @@ private JCClassDecl analyzeAndPreprocessClass(JCClassDecl tree) { @Override public void visitApply(JCMethodInvocation tree) { - List previousNascentTypes = typesUnderConstruction; - try { - Name methName = TreeInfo.name(tree.meth); - if (methName == names._this || methName == names._super) { - typesUnderConstruction = typesUnderConstruction.prepend(currentClass()); - } - super.visitApply(tree); - } finally { - typesUnderConstruction = previousNascentTypes; + super.visitApply(tree); + if (TreeInfo.isConstructorCall(tree)) { + Assert.check(typesUnderConstruction.head == currentClass()); + typesUnderConstruction = typesUnderConstruction.tail; // end of early construction context } } // where @@ -1439,13 +1434,16 @@ private LambdaTranslationContext analyzeLambda(JCLambda tree, String statKey) { @Override public void visitMethodDef(JCMethodDecl tree) { + List prevTypesUnderConstruction = typesUnderConstruction; List prevStack = frameStack; try { + if (TreeInfo.isConstructor(tree)) // start early construction context (Object() notwithstanding) + typesUnderConstruction = typesUnderConstruction.prepend(currentClass()); frameStack = frameStack.prepend(new Frame(tree)); super.visitMethodDef(tree); - } - finally { + } finally { frameStack = prevStack; + typesUnderConstruction = prevTypesUnderConstruction; } } diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Resolve.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Resolve.java index ac8374a9a55..079a0bc5c6a 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Resolve.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Resolve.java @@ -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 @@ -100,6 +100,7 @@ public class Resolve { DeferredAttr deferredAttr; Check chk; Infer infer; + Preview preview; ClassFinder finder; ModuleFinder moduleFinder; Types types; @@ -135,7 +136,7 @@ protected Resolve(Context context) { moduleFinder = ModuleFinder.instance(context); types = Types.instance(context); diags = JCDiagnostic.Factory.instance(context); - Preview preview = Preview.instance(context); + preview = Preview.instance(context); Source source = Source.instance(context); Options options = Options.instance(context); compactMethodDiags = options.isSet(Option.XDIAGS, "compact") || @@ -1480,10 +1481,11 @@ else throw new FatalError( /** Find unqualified variable or field with given name. * Synthetic fields always skipped. + * @param pos The position to use for error reporting. * @param env The current environment. * @param name The name of the variable or field. */ - Symbol findVar(Env env, Name name) { + Symbol findVar(DiagnosticPosition pos, Env env, Name name) { Symbol bestSoFar = varNotFound; Env env1 = env; boolean staticOnly = false; @@ -1508,7 +1510,7 @@ Symbol findVar(Env env, Name name) { (sym.flags() & STATIC) == 0) { if (staticOnly) return new StaticError(sym); - if (env1.info.ctorPrologue && !isAllowedEarlyReference(env1, (VarSymbol)sym)) + if (env1.info.ctorPrologue && !isAllowedEarlyReference(pos, env1, (VarSymbol)sym)) return new RefBeforeCtorCalledError(sym); } return sym; @@ -2421,15 +2423,15 @@ Symbol findType(Env env, Name name) { * (a subset of VAL, TYP, PCK). */ Symbol findIdent(DiagnosticPosition pos, Env env, Name name, KindSelector kind) { - return checkNonExistentType(checkRestrictedType(pos, findIdentInternal(env, name, kind), name)); + return checkNonExistentType(checkRestrictedType(pos, findIdentInternal(pos, env, name, kind), name)); } - Symbol findIdentInternal(Env env, Name name, KindSelector kind) { + Symbol findIdentInternal(DiagnosticPosition pos, Env env, Name name, KindSelector kind) { Symbol bestSoFar = typeNotFound; Symbol sym; if (kind.contains(KindSelector.VAL)) { - sym = findVar(env, name); + sym = findVar(pos, env, name); if (sym.exists()) return sym; else bestSoFar = bestOf(bestSoFar, sym); } @@ -3776,7 +3778,7 @@ Symbol resolveSelf(DiagnosticPosition pos, if (sym != null) { if (staticOnly) sym = new StaticError(sym); - else if (env1.info.ctorPrologue && !isAllowedEarlyReference(env1, (VarSymbol)sym)) + else if (env1.info.ctorPrologue && !isAllowedEarlyReference(pos, env1, (VarSymbol)sym)) sym = new RefBeforeCtorCalledError(sym); return accessBase(sym, pos, env.enclClass.sym.type, name, true); @@ -3845,7 +3847,7 @@ private List pruneInterfaces(Type t) { * We also don't verify that the field has no initializer, which is required. * To catch those cases, we rely on similar logic in Attr.checkAssignable(). */ - private boolean isAllowedEarlyReference(Env env, VarSymbol v) { + private boolean isAllowedEarlyReference(DiagnosticPosition pos, Env env, VarSymbol v) { // Check assumptions Assert.check(env.info.ctorPrologue); @@ -3861,12 +3863,28 @@ private boolean isAllowedEarlyReference(Env env, VarSymbol v) { // Get the symbol's qualifier, if any JCExpression lhs = TreeInfo.skipParens(assign.lhs); - JCExpression base = lhs instanceof JCFieldAccess select ? select.selected : null; + JCExpression base; + switch (lhs.getTag()) { + case IDENT: + base = null; + break; + case SELECT: + JCFieldAccess select = (JCFieldAccess)lhs; + base = select.selected; + if (!TreeInfo.isExplicitThisReference(types, (ClassType)env.enclClass.type, base)) + return false; + break; + default: + return false; + } // If an early reference, the field must not be declared in a superclass if (isEarlyReference(env, base, v) && v.owner != env.enclClass.sym) return false; + // The flexible constructors feature must be enabled + preview.checkSourceLevel(pos, Feature.FLEXIBLE_CONSTRUCTORS); + // OK return true; } diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/compiler_de.properties b/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/compiler_de.properties index 566eff4c0d5..dfbeb0fd0b7 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/compiler_de.properties +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/compiler_de.properties @@ -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 @@ -294,6 +294,9 @@ compiler.err.cant.inherit.from.final=Erben aus finalem {0}-Element nicht möglic # 0: symbol or name compiler.err.cant.ref.before.ctor.called={0} kann nicht referenziert werden, bevor der Supertypkonstruktor aufgerufen wurde +# 0: symbol or name +compiler.err.cant.assign.initialized.before.ctor.called=Initialisiertes Feld "{0}" kann nicht zugewiesen werden, bevor der Supertypkonstruktor aufgerufen wurde + compiler.err.cant.select.static.class.from.param.type=Statische Klasse kann nicht aus einem parametrisierten Typ ausgewählt werden # 0: symbol, 1: string, 2: string @@ -403,6 +406,10 @@ compiler.err.duplicate.unconditional.pattern=Doppeltes nicht bedingtes Muster compiler.err.unconditional.pattern.and.default=Switch umfasst sowohl ein nicht bedingtes Muster als auch ein Standardlabel +compiler.err.unconditional.pattern.and.both.boolean.values=Switch umfasst sowohl boolesche Werte als auch ein nicht bedingtes Muster + +compiler.err.default.and.both.boolean.values=Switch umfasst sowohl boolesche Werte als auch ein Standardlabel + compiler.err.guard.not.allowed=Guards sind nur für CASE-Anweisungen mit einem Muster zulässig compiler.err.guard.has.constant.expression.false=Dieses CASE-Label hat einen Guard, der ein konstanter Ausdruck mit dem Wert ''false'' ist @@ -413,12 +420,9 @@ compiler.err.cannot.assign.not.declared.guard=Zuweisen zu {0} nicht möglich, da # 0: type, 1: type compiler.err.constant.label.not.compatible=Konstantes Label des Typs {0} ist nicht mit Switch-Selektortyp {1} kompatibel -# 0: type -compiler.err.selector.type.not.allowed=Selektortyp {0} ist nicht zulässig - -compiler.err.flows.through.to.pattern=Ungültiger Fallthrough zu einem Muster +compiler.err.flows.through.to.pattern=Unzulässiger Fallthrough auf ein Muster\n(im vorherigen CASE-Label fehlt ein Break) -compiler.err.flows.through.from.pattern=Ungültiger Fallthrough von einem Muster +compiler.err.flows.through.from.pattern=Unzulässiger Fallthrough von einem Muster\n(im aktuellen CASE-Label fehlt ein Break) compiler.err.invalid.case.label.combination=ungültige Case-Label-Kombination @@ -533,6 +537,10 @@ compiler.err.illegal.underscore=Unzulässiger Unterstrich compiler.err.illegal.dot="." unzulässig +compiler.err.illegal.digit.in.binary.literal=Unzulässige Ziffer in einem binären Literal + +compiler.err.illegal.digit.in.octal.literal=Unzulässige Ziffer in einem oktalen Literal + # 0: symbol compiler.err.illegal.qual.not.icls=Unzulässiger Qualifier. {0} ist keine innere Klasse @@ -961,15 +969,6 @@ compiler.err.unclosed.str.lit=Nicht geschlossenes Zeichenfolgenliteral compiler.err.unclosed.text.block=Nicht geschlossener Textblock -compiler.err.string.template.is.not.well.formed=Zeichenfolgenvorlage ist nicht wohlgeformt - -compiler.err.text.block.template.is.not.well.formed=Textblockvorlage ist nicht wohlgeformt - -compiler.err.processor.missing.from.string.template.expression=Prozessor fehlt in Zeichenfolgenvorlagen-Ausdruck - -# 0: symbol -compiler.err.not.a.processor.type=Kein Prozessortyp: {0} - # 0: string compiler.err.unsupported.encoding=Nicht unterstützte Codierung: {0} @@ -1339,6 +1338,8 @@ compiler.warn.lintOption=[{0}]\u0020 # 0: symbol compiler.warn.constant.SVUID=serialVersionUID muss Konstante in Klasse {0} sein +compiler.warn.dangling.doc.comment=Dokumentationskommentar ist an keine Deklaration angehängt + # 0: path compiler.warn.dir.path.element.not.found=Ungültiges Pfadelement "{0}": Verzeichnis nicht vorhanden @@ -1901,9 +1902,6 @@ compiler.warn.prob.found.req={0}\nErforderlich: {2}\nErmittelt: {1} # 0: type, 1: type compiler.misc.inconvertible.types={0} kann nicht in {1} konvertiert werden -# 0: type, 1: type -compiler.misc.not.applicable.types=Muster des Typs {1} ist bei {0} nicht anwendbar - # 0: type, 1: type compiler.misc.possible.loss.of.precision=Möglicher Verlust bei Konvertierung von {0} in {1} @@ -2270,6 +2268,8 @@ compiler.misc.feature.deconstruction.patterns=Dekonstruktionsmuster compiler.misc.feature.unnamed.variables=Unbenannte Variablen +compiler.misc.feature.primitive.patterns=Primitive Muster + compiler.misc.feature.records=Datensätze compiler.misc.feature.sealed.classes=Verschlüsselte Klassen @@ -2278,13 +2278,13 @@ compiler.misc.feature.case.null=Null in Switch Cases compiler.misc.feature.pattern.switch=Muster in Switch-Anweisungen -compiler.misc.feature.string.templates=Zeichenfolgenvorlagen - compiler.misc.feature.unconditional.patterns.in.instanceof=Nicht bedingte Muster in instanceof compiler.misc.feature.implicit.classes=Implizit deklarierte Klassen -compiler.misc.feature.super.init=Anweisungen vor super() +compiler.misc.feature.flexible.constructors=Flexible Konstruktoren + +compiler.misc.feature.module.imports=Modulimporte compiler.warn.underscore.as.identifier=Ab Release 9 ist "_" ein Schlüsselwort und kann nicht als ID verwendet werden @@ -2502,6 +2502,15 @@ compiler.err.module.not.found=Modul nicht gefunden: {0} # 0: symbol compiler.warn.module.not.found=Modul nicht gefunden: {0} +# 0: name +compiler.err.import.module.not.found=Importiertes Modul nicht gefunden: {0} + +# 0: symbol +compiler.err.import.module.does.not.read.unnamed=Unbenanntes Modul kann Folgendes nicht lesen: {0} + +# 0: symbol, 1: symbol +compiler.err.import.module.does.not.read=Modul {0} kann Folgendes nicht lesen: {1} + compiler.err.too.many.modules=Zu viele Moduldeklarationen gefunden compiler.err.module.not.found.on.module.source.path=Modul nicht in Modulquellpfad gefunden diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/compiler_ja.properties b/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/compiler_ja.properties index 01844578905..4d98e4cabcc 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/compiler_ja.properties +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/compiler_ja.properties @@ -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 @@ -294,6 +294,9 @@ compiler.err.cant.inherit.from.final=final {0}からは継承できません # 0: symbol or name compiler.err.cant.ref.before.ctor.called=スーパータイプのコンストラクタの呼出し前は{0}を参照できません +# 0: symbol or name +compiler.err.cant.assign.initialized.before.ctor.called=スーパータイプのコンストラクタの呼出し前は、初期化されたフィールド''{0}''を割り当てられません + compiler.err.cant.select.static.class.from.param.type=パラメータにされた型からstaticクラスを選択することはできません # 0: symbol, 1: string, 2: string @@ -403,6 +406,10 @@ compiler.err.duplicate.unconditional.pattern=無条件パターンが重複し compiler.err.unconditional.pattern.and.default=switchに無条件パターンとdefaultラベルの両方があります +compiler.err.unconditional.pattern.and.both.boolean.values=switchに、ブール値と無条件パターンの両方があります + +compiler.err.default.and.both.boolean.values=switchに、ブール値とdefaultラベルの両方があります + compiler.err.guard.not.allowed=ガードはパターンのあるcaseでのみ許可されます compiler.err.guard.has.constant.expression.false=このcaseラベルには、値が''false''の定数式であるガードがあります @@ -413,12 +420,9 @@ compiler.err.cannot.assign.not.declared.guard=ガード内で宣言されてい # 0: type, 1: type compiler.err.constant.label.not.compatible=タイプ{0}の定数ラベルがswitchセレクタ・タイプ{1}と互換性がありません -# 0: type -compiler.err.selector.type.not.allowed=セレクタ・タイプ{0}は許可されません - -compiler.err.flows.through.to.pattern=パターンに対して不正なfall-through +compiler.err.flows.through.to.pattern=パターンに対して不正なfall-through\n(前のcaseラベルにbreakがありません) -compiler.err.flows.through.from.pattern=パターンからの不正なfall-through +compiler.err.flows.through.from.pattern=パターンに対して不正なfall-through\n(現在のcaseラベルにbreakがありません) compiler.err.invalid.case.label.combination=caseラベルの組合せが無効です @@ -533,6 +537,10 @@ compiler.err.illegal.underscore=不正なアンダースコアです compiler.err.illegal.dot=不正な''.''です +compiler.err.illegal.digit.in.binary.literal=2進数リテラルの数字が正しくありません + +compiler.err.illegal.digit.in.octal.literal=8進数リテラルの数字が正しくありません + # 0: symbol compiler.err.illegal.qual.not.icls=修飾子が不正です。{0}は内部クラスではありません @@ -961,15 +969,6 @@ compiler.err.unclosed.str.lit=文字列リテラルが閉じられていませ compiler.err.unclosed.text.block=閉じられていないテキスト・ブロック -compiler.err.string.template.is.not.well.formed=文字列テンプレートが整形式ではありません - -compiler.err.text.block.template.is.not.well.formed=テキスト・ブロック・テンプレートが整形式ではありません - -compiler.err.processor.missing.from.string.template.expression=プロセッサが文字列テンプレート式にありません - -# 0: symbol -compiler.err.not.a.processor.type=プロセッサ・タイプではありません: {0} - # 0: string compiler.err.unsupported.encoding=サポートされていないエンコーディングです: {0} @@ -1339,6 +1338,8 @@ compiler.warn.lintOption=[{0}]\u0020 # 0: symbol compiler.warn.constant.SVUID=serialVersionUIDはクラス{0}の定数である必要があります +compiler.warn.dangling.doc.comment=どの宣言にもドキュメンテーション・コメントが添付されていません + # 0: path compiler.warn.dir.path.element.not.found=不正なパス要素"{0}": そのディレクトリは存在しません @@ -1901,9 +1902,6 @@ compiler.warn.prob.found.req={0}\n期待値: {2}\n検出値: {1} # 0: type, 1: type compiler.misc.inconvertible.types={0}を{1}に変換できません: -# 0: type, 1: type -compiler.misc.not.applicable.types=型{1}のパターンは{0}では適用できません - # 0: type, 1: type compiler.misc.possible.loss.of.precision=精度が失われる可能性がある{0}から{1}への変換 @@ -2270,6 +2268,8 @@ compiler.misc.feature.deconstruction.patterns=デコンストラクション・ compiler.misc.feature.unnamed.variables=無名変数 +compiler.misc.feature.primitive.patterns=プリミティブ・パターン + compiler.misc.feature.records=レコード compiler.misc.feature.sealed.classes=シール・クラス @@ -2278,13 +2278,13 @@ compiler.misc.feature.case.null=switch caseのnull compiler.misc.feature.pattern.switch=switch文のパターン -compiler.misc.feature.string.templates=文字列テンプレート - compiler.misc.feature.unconditional.patterns.in.instanceof=instanceofでの無条件パターン compiler.misc.feature.implicit.classes=暗黙的に宣言されたクラス -compiler.misc.feature.super.init=super()の前の文 +compiler.misc.feature.flexible.constructors=柔軟なコンストラクタ + +compiler.misc.feature.module.imports=モジュール・インポート compiler.warn.underscore.as.identifier=リリース9から''_''はキーワードなので識別子として使用することはできません @@ -2502,6 +2502,15 @@ compiler.err.module.not.found=モジュールが見つかりません: {0} # 0: symbol compiler.warn.module.not.found=モジュールが見つかりません: {0} +# 0: name +compiler.err.import.module.not.found=インポートされたモジュールが見つかりません: {0} + +# 0: symbol +compiler.err.import.module.does.not.read.unnamed=名前のないモジュールでは読取りは行われません: {0} + +# 0: symbol, 1: symbol +compiler.err.import.module.does.not.read=モジュール{0}では読取りは行われません: {1} + compiler.err.too.many.modules=検出されたモジュール宣言が多すぎます compiler.err.module.not.found.on.module.source.path=モジュール・ソース・パスにモジュールが見つかりません diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/compiler_zh_CN.properties b/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/compiler_zh_CN.properties index 99c5df09fc1..81452f23a0d 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/compiler_zh_CN.properties +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/compiler_zh_CN.properties @@ -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 @@ -294,6 +294,9 @@ compiler.err.cant.inherit.from.final=无法从最终{0}进行继承 # 0: symbol or name compiler.err.cant.ref.before.ctor.called=无法在调用超类型构造器之前引用{0} +# 0: symbol or name +compiler.err.cant.assign.initialized.before.ctor.called=无法在调用超类型构造器之前分配初始化字段 ''{0}'' + compiler.err.cant.select.static.class.from.param.type=无法从参数化的类型中选择静态类 # 0: symbol, 1: string, 2: string @@ -403,6 +406,10 @@ compiler.err.duplicate.unconditional.pattern=无条件模式重复 compiler.err.unconditional.pattern.and.default=switch 有一个无条件模式和一个 default 标签 +compiler.err.unconditional.pattern.and.both.boolean.values=switch 有布尔值和一个无条件模式 + +compiler.err.default.and.both.boolean.values=switch 有布尔值和一个 default 标签 + compiler.err.guard.not.allowed=只有包含模式的 case 允许使用卫士 compiler.err.guard.has.constant.expression.false=此 case 标签有一个卫士,它是值为 ''false'' 的常量表达式 @@ -413,16 +420,13 @@ compiler.err.cannot.assign.not.declared.guard=无法分配给 {0},因为未在 # 0: type, 1: type compiler.err.constant.label.not.compatible={0} 类型的常量标签与 switch 选择器类型 {1} 不兼容 -# 0: type -compiler.err.selector.type.not.allowed=不允许使用选择器类型 {0} - -compiler.err.flows.through.to.pattern=贯穿 (fall-through) 到模式非法 +compiler.err.flows.through.to.pattern=贯穿 (fall-through) 到模式非法\n(上一个 case 标签缺少中断) -compiler.err.flows.through.from.pattern=从模式贯穿 (fall-through) 非法 +compiler.err.flows.through.from.pattern=从模式贯穿 (fall-through) 非法\n(当前 case 标签缺少中断) compiler.err.invalid.case.label.combination=case 标签组合无效 -compiler.err.default.label.not.allowed=此处不允许使用默认标签 +compiler.err.default.label.not.allowed=此处不允许使用 default 标签 compiler.err.pattern.type.cannot.infer=无法推断模式类型 @@ -533,6 +537,10 @@ compiler.err.illegal.underscore=非法下划线 compiler.err.illegal.dot=非法 ''.'' +compiler.err.illegal.digit.in.binary.literal=二进制文字中的数字非法 + +compiler.err.illegal.digit.in.octal.literal=八进制文字中的数字非法 + # 0: symbol compiler.err.illegal.qual.not.icls=非法限定符; {0}不是内部类 @@ -961,15 +969,6 @@ compiler.err.unclosed.str.lit=未结束的字符串文字 compiler.err.unclosed.text.block=文本块未闭合 -compiler.err.string.template.is.not.well.formed=字符串模板格式不正确 - -compiler.err.text.block.template.is.not.well.formed=文本块模板格式不正确 - -compiler.err.processor.missing.from.string.template.expression=字符串模板表达式缺少处理程序 - -# 0: symbol -compiler.err.not.a.processor.type=不是处理程序类型:{0} - # 0: string compiler.err.unsupported.encoding=不支持的编码: {0} @@ -1339,6 +1338,8 @@ compiler.warn.lintOption=[{0}]\u0020 # 0: symbol compiler.warn.constant.SVUID=serialVersionUID 在类{0}中必须是常量 +compiler.warn.dangling.doc.comment=文档注释未附加到任何声明 + # 0: path compiler.warn.dir.path.element.not.found=错误的路径元素 "{0}": 没有这种目录 @@ -1901,9 +1902,6 @@ compiler.warn.prob.found.req={0}\n需要: {2}\n找到: {1} # 0: type, 1: type compiler.misc.inconvertible.types={0}无法转换为{1} -# 0: type, 1: type -compiler.misc.not.applicable.types=类型为 {1} 的模式不适用于 {0} - # 0: type, 1: type compiler.misc.possible.loss.of.precision=从{0}转换到{1}可能会有损失 @@ -2270,6 +2268,8 @@ compiler.misc.feature.deconstruction.patterns=解构模式 compiler.misc.feature.unnamed.variables=未命名变量 +compiler.misc.feature.primitive.patterns=基元模式 + compiler.misc.feature.records=记录 compiler.misc.feature.sealed.classes=密封类 @@ -2278,13 +2278,13 @@ compiler.misc.feature.case.null=switch case 中的空值 compiler.misc.feature.pattern.switch=switch 语句中的模式 -compiler.misc.feature.string.templates=字符串模板 - compiler.misc.feature.unconditional.patterns.in.instanceof=instanceof 中的无条件模式 compiler.misc.feature.implicit.classes=隐式声明的类 -compiler.misc.feature.super.init=super() 之前的语句 +compiler.misc.feature.flexible.constructors=灵活构造器 + +compiler.misc.feature.module.imports=模块导入 compiler.warn.underscore.as.identifier=从发行版 9 开始, ''_'' 为关键字, 不能用作标识符 @@ -2502,6 +2502,15 @@ compiler.err.module.not.found=找不到模块: {0} # 0: symbol compiler.warn.module.not.found=找不到模块: {0} +# 0: name +compiler.err.import.module.not.found=找不到导入的模块:{0} + +# 0: symbol +compiler.err.import.module.does.not.read.unnamed=未命名模块未读取:{0} + +# 0: symbol, 1: symbol +compiler.err.import.module.does.not.read=模块 {0} 未读取:{1} + compiler.err.too.many.modules=找到太多的模块声明 compiler.err.module.not.found.on.module.source.path=在模块源路径中找不到模块 diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/javac_de.properties b/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/javac_de.properties index cc3bea6c763..00b64b1f099 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/javac_de.properties +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/javac_de.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 1999, 2022, 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 @@ -109,6 +109,8 @@ javac.opt.Xlint.desc.cast=Warnt vor unnötigen Umwandlungen mit Cast. javac.opt.Xlint.desc.classfile=Warnt vor Problemen im Zusammenhang mit Klassendateiinhalten. +javac.opt.Xlint.desc.dangling-doc-comments=Warnt vor Dokumentationskommentaren, die an keine Deklaration angehängt sind. + javac.opt.Xlint.desc.missing-explicit-ctor=Warnt vor fehlenden expliziten Konstruktoren in öffentlichen und geschützten Klassen in exportierten Packages. javac.opt.Xlint.desc.deprecation=Warnt vor der Verwendung veralteter Elemente. @@ -214,6 +216,7 @@ javac.opt.module.version=Gibt die Version der Module an, die kompiliert werden javac.opt.arg.module.version= javac.opt.inherit_runtime_environment=Vererbt Modulsystemkonfigurationsoptionen aus der Laufzeitumgebung. javac.opt.default.module.for.created.files=Fallback-Zielmodul für Dateien, die von Annotationsprozessoren erstellt werden,\nfalls keines angegeben ist oder abgeleitet werden kann. +javac.opt.lineDocComments=Unterstützung für Dokumentationskommentare mit Zeilen, die mit "///" beginnen, deaktivieren ## messages diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/javac_ja.properties b/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/javac_ja.properties index f33c2c54cea..4eb259b23cc 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/javac_ja.properties +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/javac_ja.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 1999, 2022, 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 @@ -109,6 +109,8 @@ javac.opt.Xlint.desc.cast=不要なキャストの使用について警告しま javac.opt.Xlint.desc.classfile=クラス・ファイルの内容に関連した問題について警告します。 +javac.opt.Xlint.desc.dangling-doc-comments=どの宣言にも添付されていないドキュメンテーション・コメントについて警告します。 + javac.opt.Xlint.desc.missing-explicit-ctor=エクスポートされたパッケージのpublicおよびprotectedのクラスに明示的なコンストラクタがないことを警告します。 javac.opt.Xlint.desc.deprecation=非推奨項目の使用について警告します。 @@ -214,6 +216,7 @@ javac.opt.module.version=コンパイルするモジュールのバージョン javac.opt.arg.module.version=<バージョン> javac.opt.inherit_runtime_environment=実行時環境からモジュール・システム構成オプションを継承します。 javac.opt.default.module.for.created.files=何も指定されていないか、推定型の場合、注釈プロセッサによって作成されるファイルのターゲット・モジュールをフォールバックします。 +javac.opt.lineDocComments='///'で行を開始すると、ドキュメンテーション・コメントのサポートが無効化されます ## messages diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/javac_zh_CN.properties b/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/javac_zh_CN.properties index 64e031ed381..8cbddae86f6 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/javac_zh_CN.properties +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/javac_zh_CN.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 1999, 2022, 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 @@ -109,6 +109,8 @@ javac.opt.Xlint.desc.cast=有关使用了不必要转换的警告。 javac.opt.Xlint.desc.classfile=有关与类文件内容相关的问题的警告。 +javac.opt.Xlint.desc.dangling-doc-comments=有关未附加到任何声明的悬挂文档注释的警告。 + javac.opt.Xlint.desc.missing-explicit-ctor=有关导出的程序包中的公共和受保护类中缺少显式构造器的警告。 javac.opt.Xlint.desc.deprecation=有关使用了已过时项的警告。 @@ -214,6 +216,7 @@ javac.opt.module.version=指定正在编译的模块版本 javac.opt.arg.module.version=<版本> javac.opt.inherit_runtime_environment=从运行时环境继承模块系统配置选项。 javac.opt.default.module.for.created.files=由批注处理程序创建的文件的备用目标模块 (如果未指定或推断任何模块)。 +javac.opt.lineDocComments=禁用对带有以 '///' 开头的行的文档注释的支持 ## messages diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/launcher_de.properties b/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/launcher_de.properties index b59b227ddcb..efcc594ca6c 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/launcher_de.properties +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/launcher_de.properties @@ -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 @@ -122,8 +122,6 @@ launcher.err.no.value.for.option=kein Wert angegeben für Option: {0} # 0: string launcher.err.invalid.value.for.source=ungültiger Wert für Option --source: {0} -launcher.err.enable.preview.requires.source=--enable-preview muss mit --source verwendet werden - launcher.err.unnamed.pkg.not.allowed.named.modules=Unbenanntes Package ist in benannten Modulen nicht zulässig # 0: string, 1: path diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/launcher_ja.properties b/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/launcher_ja.properties index 8bb25907bcd..72e2478bff3 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/launcher_ja.properties +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/launcher_ja.properties @@ -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 @@ -122,8 +122,6 @@ launcher.err.no.value.for.option=オプションに値が指定されていま # 0: string launcher.err.invalid.value.for.source=--sourceオプションの値が無効です: {0} -launcher.err.enable.preview.requires.source=--enable-previewは--sourceとともに使用する必要があります - launcher.err.unnamed.pkg.not.allowed.named.modules=名前のないパッケージは名前付きモジュールでは許可されません # 0: string, 1: path diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/launcher_zh_CN.properties b/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/launcher_zh_CN.properties index 81976d40b86..ee666cc02c1 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/launcher_zh_CN.properties +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/launcher_zh_CN.properties @@ -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 @@ -122,8 +122,6 @@ launcher.err.no.value.for.option=没有为选项 {0} 指定值 # 0: string launcher.err.invalid.value.for.source=--source 选项的值无效:{0}\n -launcher.err.enable.preview.requires.source=--enable-preview 必须与 --source 一起使用 - launcher.err.unnamed.pkg.not.allowed.named.modules=命名模块中不允许未命名程序包 # 0: string, 1: path diff --git a/src/jdk.hotspot.agent/linux/native/libsaproc/ps_proc.c b/src/jdk.hotspot.agent/linux/native/libsaproc/ps_proc.c index 3068f475626..41207b5a9df 100644 --- a/src/jdk.hotspot.agent/linux/native/libsaproc/ps_proc.c +++ b/src/jdk.hotspot.agent/linux/native/libsaproc/ps_proc.c @@ -242,7 +242,7 @@ static bool process_doesnt_exist(pid_t pid) { found_state = true; state = buf + state_len; // Skip the spaces - while (isspace(*state)) { + while (isspace((unsigned char) *state)) { state++; } // A state value of 'X' indicates that the thread is dead. 'Z' diff --git a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/tools/HeapSummary.java b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/tools/HeapSummary.java index 41788757e0c..22eb627d39f 100644 --- a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/tools/HeapSummary.java +++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/tools/HeapSummary.java @@ -80,7 +80,6 @@ public void run() { printValMB("MaxHeapSize = ", getFlagValue("MaxHeapSize", flagMap)); printValMB("NewSize = ", getFlagValue("NewSize", flagMap)); printValMB("MaxNewSize = ", getFlagValue("MaxNewSize", flagMap)); - printValMB("OldSize = ", getFlagValue("OldSize", flagMap)); printValue("NewRatio = ", getFlagValue("NewRatio", flagMap)); printValue("SurvivorRatio = ", getFlagValue("SurvivorRatio", flagMap)); printValMB("MetaspaceSize = ", getFlagValue("MetaspaceSize", flagMap)); diff --git a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotJVMCIRuntime.java b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotJVMCIRuntime.java index 87cd10b400f..3dcf855ff23 100644 --- a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotJVMCIRuntime.java +++ b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotJVMCIRuntime.java @@ -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 @@ -987,6 +987,12 @@ private boolean isGCSupported(int gcIdentifier) { return getCompiler().isGCSupported(gcIdentifier); } + @SuppressWarnings("try") + @VMEntryPoint + private boolean isIntrinsicSupported(int intrinsicIdentifier) { + return getCompiler().isIntrinsicSupported(intrinsicIdentifier); + } + /** * Guard to ensure shut down actions are performed by at most one thread. */ diff --git a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotVMConfigStore.java b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotVMConfigStore.java index 0ee69f135c9..c6735cef5f6 100644 --- a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotVMConfigStore.java +++ b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotVMConfigStore.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2018, 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 @@ -82,6 +82,27 @@ public List getIntrinsics() { return Collections.unmodifiableList(vmIntrinsics); } + /** + * Gets the VM intrinsic description by its ID. + */ + public VMIntrinsicMethod getIntrinsic(int intrinsicID) { + if (intrinsicID >= 1 && intrinsicID <= vmIntrinsics.size()) { + // valid intrinsicID starts from 1 + VMIntrinsicMethod intrinsic = vmIntrinsics.get(intrinsicID - 1); + // We speculate that vmIntrinsics are sorted by ID + if (intrinsic.id == intrinsicID) { + return intrinsic; + } + } + // Assumption failed, fall back to iteration + for (VMIntrinsicMethod intrinsic : vmIntrinsics) { + if (intrinsic.id == intrinsicID) { + return intrinsic; + } + } + return null; + } + final HashMap vmFields; final HashMap vmConstants; final HashMap vmAddresses; diff --git a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/runtime/JVMCICompiler.java b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/runtime/JVMCICompiler.java index bbb9c79e9f5..accd9e01490 100644 --- a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/runtime/JVMCICompiler.java +++ b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/runtime/JVMCICompiler.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 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 @@ -44,4 +44,15 @@ public interface JVMCICompiler { default boolean isGCSupported(int gcIdentifier) { return true; } + + /** + * Determines if this compiler supports the {@code intrinsicIdentifier} intrinsic. The default + * implementation of this method returns false as that is the effective answer given by a + * {@link JVMCICompiler} before this method was added. + * + * @param intrinsicIdentifier intrinsic identifier defined in vmIntrinsics.hpp. + */ + default boolean isIntrinsicSupported(int intrinsicIdentifier) { + return false; + } } diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/standard_de.properties b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/standard_de.properties index 1bdbb5d0614..c444f387a65 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/standard_de.properties +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/standard_de.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2010, 2022, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2010, 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,17 +116,20 @@ doclet.tag.invalid_input=Ungültige Eingabe: "{0}" doclet.tag.invalid=ungültiges @{0} doclet.Deprecated_API=Veraltete API doclet.Deprecated_API_Checkbox_Label=Veraltete API anzeigen in: +doclet.Deprecated_API_Checkbox_All_Releases=Alle doclet.Deprecated_API_Checkbox_Other_Releases=Sonstige doclet.Deprecated_Elements=Veraltete {0} doclet.Deprecated_Elements_Release_Column_Header=Veraltet in doclet.Deprecated_In_Release=Veraltet in {0} doclet.New_API=Neue API doclet.New_API_Checkbox_Label=Hinzugefügte API anzeigen in: +doclet.New_API_Checkbox_All_Releases=Alle doclet.New_Elements=Neue {0} doclet.New_Elements_Release_Column_Header=Hinzugefügt in doclet.New_Label=Neu doclet.Preview_API=Vorschau-API doclet.Preview_API_Checkbox_Label=Vorschau-API anzeigen für: +doclet.Preview_API_Checkbox_Toggle_All=Alle umschalten doclet.Preview_JEP_URL=https://openjdk.org/jeps/{0} doclet.Preview_Label=Vorschau doclet.Preview_Mark=PREVIEW @@ -229,7 +232,7 @@ doclet.help.class_interface.property=Eigenschaften sind ein Feature von JavaFX. doclet.help.other_files.head=Weitere Dateien doclet.help.other_files.body=Packages und Module können Seiten mit weiteren Informationen zu den Deklarationen in der Nähe enthalten. doclet.help.use.head=Verwendung -doclet.help.use.body=Für jedes dokumentierte Package sowie jede Klasse und jede Schnittstelle ist eine eigene Verwendungsseite vorhanden. Auf dieser Seite wird beschrieben, welche Packages, Klassen, Methoden, Konstruktoren und Felder einen Teil der angegebenen Klasse oder des angegebenen Packages verwenden. Bei der Klasse oder Schnittstelle A enthält die Verwendungsseite die Unterklassen von A, als A deklarierte Felder, Methoden, die A zurückgeben, sowie Methoden und Konstruktoren mit Parametern des Typs A. Sie können diese Seite aufrufen, indem Sie zunächst das Package, die Klasse oder die Schnittstelle aufrufen und anschließend in der Navigationsleiste auf den Link "Verwendung" klicken. +doclet.help.use.body=Für jedes dokumentierte Package sowie jede Klasse oder Schnittstelle ist eine eigene Verwendungsseite vorhanden. Auf dieser Seite werden die Packages, Klassen, Schnittstellen, Methoden, Konstruktoren und Felder aufgelistet, die einen Teil der angegebenen Klasse oder Schnittstelle oder des angegebenen Packages verwenden. Bei der Klasse oder Schnittstelle A enthält die Verwendungsseite die Unterklassen oder Unterschnittstellen von A, als A deklarierte Felder, Methoden, die A zurückgeben, Methoden und Konstruktoren mit Parametern des Typs A und Unterklassen oder Unterschnittstellen mit Parametern des Typs A. Sie können auf diese Seite zugreifen, indem Sie zunächst das Package, die Klasse oder die Schnittstelle aufrufen und anschließend in der Navigationsleiste auf den Link "Verwendung" klicken. doclet.help.tree.head=Baum (Klassenhierarchie) # 0: link to main Class Hierarchy page; 1: java.lang.Object doclet.help.tree.intro=Es gibt eine Seite {0} für alle Packages, und für jedes Package gibt es eine Hierarchie. Jede Hierarchieseite enthält eine Klassen- und eine Schnittstellenliste. Die Klassen sind nach Vererbungsstruktur organisiert, beginnend mit {1}. Die Schnittstellen erben nicht von {1}. @@ -284,6 +287,9 @@ doclet.ClassUse_Classes.in.0.used.by.1=Von {1} verwendete Klassen in {0} doclet.ClassUse_PackageAnnotation=Packages mit Annotationen vom Typ {0} doclet.ClassUse_Annotation=Klassen in {1} mit Annotationen vom Typ {0} doclet.ClassUse_TypeParameter=Klassen in {1} mit Typparametern vom Typ {0} +doclet.ClassUse_SubclassTypeParameter=Unterklassen mit Typargumenten vom Typ {0} in {1} +doclet.ClassUse_SubinterfaceTypeParameter=Unterschnittstellen mit Typargumenten vom Typ {0} in {1} +doclet.ClassUse_ImplementsTypeParameter=Klassen in {1}, die Schnittstellen mit Typargumenten vom Typ {0} implementieren doclet.ClassUse_MethodTypeParameter=Methoden in {1} mit Typparametern vom Typ {0} doclet.ClassUse_FieldTypeParameter=Felder in {1} mit Typparametern vom Typ {0} doclet.ClassUse_FieldAnnotations=Felder in {1} mit Annotationen vom Typ {0} @@ -412,6 +418,8 @@ doclet.usage.nocomment.description=Beschreibung und Tags unterdrücken und nur D doclet.usage.nodeprecated.description=@deprecated-Informationen nicht aufnehmen +doclet.usage.no-fonts.description=Keine Standardwebschriftarten in generierte Dokumentation aufnehmen + doclet.usage.noqualifier.parameters=,,... doclet.usage.noqualifier.description=Liste der Qualifier aus der Ausgabe ausschließen.\n":" kann überall im Argument als Trennzeichen verwendet werden. diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/standard_ja.properties b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/standard_ja.properties index 1764587769b..ce56bc16325 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/standard_ja.properties +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/standard_ja.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2010, 2022, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2010, 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,17 +116,20 @@ doclet.tag.invalid_input=入力が無効です: ''{0}'' doclet.tag.invalid=@{0}が無効です doclet.Deprecated_API=推奨されていないAPI doclet.Deprecated_API_Checkbox_Label=次で非推奨のAPIを表示: +doclet.Deprecated_API_Checkbox_All_Releases=すべて doclet.Deprecated_API_Checkbox_Other_Releases=その他 doclet.Deprecated_Elements=推奨されていない{0} doclet.Deprecated_Elements_Release_Column_Header=次で非推奨 doclet.Deprecated_In_Release={0}で非推奨 doclet.New_API=新規API doclet.New_API_Checkbox_Label=次で追加されたAPIを表示: +doclet.New_API_Checkbox_All_Releases=すべて doclet.New_Elements=新規{0} doclet.New_Elements_Release_Column_Header=次で追加 doclet.New_Label=新規 doclet.Preview_API=プレビューAPI doclet.Preview_API_Checkbox_Label=次のプレビューAPIを表示: +doclet.Preview_API_Checkbox_Toggle_All=すべて設定 doclet.Preview_JEP_URL=https://openjdk.org/jeps/{0} doclet.Preview_Label=プレビュー doclet.Preview_Mark=PREVIEW @@ -229,7 +232,7 @@ doclet.help.class_interface.property=プロパティはJavaFXの機能です。 doclet.help.other_files.head=その他のファイル doclet.help.other_files.body=パッケージおよびモジュールには、周辺の宣言に関連する追加情報が記載されているページが含まれている場合があります。 doclet.help.use.head=使用 -doclet.help.use.body=各ドキュメント化されたパッケージ、クラスおよびインタフェースにはそれぞれ「使用」ページがあります。このページには、どのようなパッケージ、クラス、メソッド、コンストラクタおよびフィールドが、特定のクラスまたはパッケージの一部を使用しているかが記述されています。たとえば、クラスAまたはインタフェースAの場合、その「使用」ページには、Aのサブクラス、Aとして宣言されるフィールド、Aを返すメソッドと、型Aのパラメータを持つメソッドおよびコンストラクタが含まれます。このページにアクセスするには、まずそのパッケージ、クラスまたはインタフェースに移動し、ナビゲーション・バーの「使用」リンクをクリックしてください。 +doclet.help.use.body=ドキュメント化された各パッケージ、クラスまたはインタフェースには、それぞれに「使用」ページがあります。このページにリストされているパッケージ、クラス、インタフェース、メソッド、コンストラクタおよびフィールドでは、ドキュメント化されたパッケージ、クラスまたはインタフェースの一部が使用されています。たとえば、クラスAまたはインタフェースAの場合、その「使用」ページには、Aのサブクラスまたはインタフェース、Aとして宣言されるフィールド、Aを返すメソッドと、型Aのパラメータが指定されたメソッドとコンストラクタ、および型Aのパラメータが指定されたサブクラスまたはサブインタフェースが含まれます。このページにアクセスするには、まずそのパッケージ、クラスまたはインタフェースに移動し、ナビゲーション・バーの「使用」リンクをクリックしてください。 doclet.help.tree.head=階層ツリー(クラス階層) # 0: link to main Class Hierarchy page; 1: java.lang.Object doclet.help.tree.intro=すべてのパッケージには{0}ページがあり、さらに各パッケージの階層があります。各階層ページは、クラスのリストとインタフェースのリストを含みます。クラスは{1}を開始点とする継承構造で編成されます。インタフェースは、{1}からは継承しません。 @@ -284,6 +287,9 @@ doclet.ClassUse_Classes.in.0.used.by.1={1}により使用される{0}のクラ doclet.ClassUse_PackageAnnotation={0}型の注釈を持つパッケージ doclet.ClassUse_Annotation={0}型の注釈を持つ{1}のメソッド doclet.ClassUse_TypeParameter={0}型の型パラメータを持つ{1}のクラス +doclet.ClassUse_SubclassTypeParameter={0}型の型引数を持つ{1}のサブクラス +doclet.ClassUse_SubinterfaceTypeParameter={0}型の型引数を持つ{1}のサブインタフェース +doclet.ClassUse_ImplementsTypeParameter={0}型の型引数を持つインタフェースを実装している{1}のクラス doclet.ClassUse_MethodTypeParameter={0}型の型パラメータを持つ{1}のメソッド doclet.ClassUse_FieldTypeParameter={0}型の型パラメータを持つ{1}のフィールド doclet.ClassUse_FieldAnnotations={0}型の注釈を持つ{1}のフィールド @@ -412,6 +418,8 @@ doclet.usage.nocomment.description=記述およびタグを抑制して宣言の doclet.usage.nodeprecated.description=@deprecated情報を除外します +doclet.usage.no-fonts.description=生成されるドキュメントに、標準にwebフォントを使用しないでください + doclet.usage.noqualifier.parameters=,,... doclet.usage.noqualifier.description=出力から修飾子のリストを除外します。\n':'も、セパレータとして引数の任意の場所に使用できます。 diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/standard_zh_CN.properties b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/standard_zh_CN.properties index 5dcd3dce996..c109d4c4237 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/standard_zh_CN.properties +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/standard_zh_CN.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2010, 2022, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2010, 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,17 +116,20 @@ doclet.tag.invalid_input=无效输入:''{0}'' doclet.tag.invalid=@{0} 无效 doclet.Deprecated_API=已过时的 API doclet.Deprecated_API_Checkbox_Label=显示在以下发行版中已过时的 API: +doclet.Deprecated_API_Checkbox_All_Releases=全部 doclet.Deprecated_API_Checkbox_Other_Releases=其他 doclet.Deprecated_Elements=已过时的 {0} doclet.Deprecated_Elements_Release_Column_Header=在以下发行版中已过时 doclet.Deprecated_In_Release=在 {0} 中已过时 doclet.New_API=新建 API doclet.New_API_Checkbox_Label=显示在以下发行版中添加的 API: +doclet.New_API_Checkbox_All_Releases=全部 doclet.New_Elements=新建 {0} doclet.New_Elements_Release_Column_Header=在以下发行版中已添加 doclet.New_Label=新建 doclet.Preview_API=预览 API doclet.Preview_API_Checkbox_Label=显示预览 API: +doclet.Preview_API_Checkbox_Toggle_All=全部切换 doclet.Preview_JEP_URL=https://openjdk.org/jeps/{0} doclet.Preview_Label=预览 doclet.Preview_Mark=PREVIEW @@ -229,7 +232,7 @@ doclet.help.class_interface.property=属性是 JavaFX 的一个特性。 doclet.help.other_files.head=其他文件 doclet.help.other_files.body=程序包和模块所包含的页面中可能带有与附近声明相关的附加信息。 doclet.help.use.head=使用 -doclet.help.use.body=每个已文档化的程序包、类和接口都有各自的“使用”页面。此页面介绍了使用给定类或程序包的任何部分的程序包、类、方法、构造器和字段。对于给定的类或接口 A,其“使用”页面包含 A 的子类、声明为 A 的字段、返回 A 的方法,以及带有类型为 A 的参数的方法和构造器。访问此页面的方法是:首先转至程序包、类或接口,然后单击导航栏中的“使用”链接。 +doclet.help.use.body=每个已文档化的程序包、类或接口都有各自的“使用”页面,其中列出了使用该程序包、类或接口的任何部分的程序包、类、接口、方法、构造器和字段。对于给定的类或接口 A,其“使用”页面包含 A 的子类或子接口、声明为 A 的字段、返回 A 的方法、带有类型为 A 的参数的方法和构造器,以及带有类型为 A 的参数的子类或子接口。访问此页面的方法是:首先转至程序包、类或接口,然后单击导航栏中的“使用”链接。 doclet.help.tree.head=树 (类分层结构) # 0: link to main Class Hierarchy page; 1: java.lang.Object doclet.help.tree.intro=对于所有程序包,都有一个 {0} 页,以及每个程序包的分层结构。每个分层结构页都包含类的列表和接口的列表。从 {1} 开始,按继承结构对类进行排列。接口不从 {1} 继承。 @@ -284,6 +287,9 @@ doclet.ClassUse_Classes.in.0.used.by.1={1}使用的{0}中的类 doclet.ClassUse_PackageAnnotation=批注类型为{0}的程序包 doclet.ClassUse_Annotation=批注类型为{0}的{1}中的类 doclet.ClassUse_TypeParameter=类型参数类型为{0}的{1}中的类 +doclet.ClassUse_SubclassTypeParameter={1} 中类型参数类型为 {0} 的子类 +doclet.ClassUse_SubinterfaceTypeParameter={1} 中类型参数类型为 {0} 的子接口 +doclet.ClassUse_ImplementsTypeParameter={1} 中实现类型参数类型为 {0} 的接口的类 doclet.ClassUse_MethodTypeParameter=类型参数类型为{0}的{1}中的方法 doclet.ClassUse_FieldTypeParameter=类型参数类型为{0}的{1}中的字段 doclet.ClassUse_FieldAnnotations=批注类型为{0}的{1}中的字段 @@ -412,6 +418,8 @@ doclet.usage.nocomment.description=不生成说明和标记, 只生成声明 doclet.usage.nodeprecated.description=不包含 @deprecated 信息 +doclet.usage.no-fonts.description=不要在生成的文档中包含标准 Web 字体 + doclet.usage.noqualifier.parameters=,,... doclet.usage.noqualifier.description=在输出中排除一系列限定符。\n还可以将 ':' 作为分隔符用于参数中的任何位置。 diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/resources/doclets_de.properties b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/resources/doclets_de.properties index 242be92fc8c..4e84cdc52c7 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/resources/doclets_de.properties +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/resources/doclets_de.properties @@ -249,6 +249,7 @@ doclet.linkMismatch_ModuleLinkedtoPackage=Der Code, der dokumentiert wird, verwe doclet.urlRedirected=URL {0} wurde umgeleitet an {1} - Aktualisieren Sie die Befehlszeilenoptionen, um diese Warnung zu unterdrücken. doclet.unexpectedRedirect=Unerwartete Umleitung für URL {0} zu {1} doclet.duplicate.comment.for.property=Doppelter Kommentar für Eigenschaft.\nEntfernen Sie den Kommentar auf dem Eigenschaftsfeld oder in dieser Methode, um diese Warnung zu unterdrücken. +doclet.contains.diagnostic.markers=Die generierte Dokumentation enthält Diagnosemarker für eine ungültige Eingabe. #Documentation for Enums doclet.enum_values_doc.fullbody=Gibt ein Array mit den Konstanten dieses Enum-Typs in\nder Reihenfolge ihrer Deklaration zurück. diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/resources/doclets_ja.properties b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/resources/doclets_ja.properties index 986d3affe1f..515df706272 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/resources/doclets_ja.properties +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/resources/doclets_ja.properties @@ -249,6 +249,7 @@ doclet.linkMismatch_ModuleLinkedtoPackage=ドキュメント化しようとし doclet.urlRedirected=URL {0}は{1}にリダイレクトされました -- コマンドライン・オプションを更新してこの警告を表示しないようにしてください。 doclet.unexpectedRedirect=URL {0}から{1}への予期しないリダイレクト doclet.duplicate.comment.for.property=プロパティのコメントが重複して\nいます。プロパティ・フィールドまたはこのメソッドのコメントを削除してこの警告を出さないようにしてください。 +doclet.contains.diagnostic.markers=生成されたドキュメンテーションには、無効な入力の診断マーカーがあります。 #Documentation for Enums doclet.enum_values_doc.fullbody=この列挙型の定数を含む配列を宣言されている順序で\n返します。 diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/resources/doclets_zh_CN.properties b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/resources/doclets_zh_CN.properties index 99a31b3eec5..d01d42faa56 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/resources/doclets_zh_CN.properties +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/resources/doclets_zh_CN.properties @@ -249,6 +249,7 @@ doclet.linkMismatch_ModuleLinkedtoPackage=进行文档化的代码使用了模 doclet.urlRedirected=URL {0} 已重定向到 {1} — 更新命令行选项以隐藏此警告。 doclet.unexpectedRedirect=URL {0} 意外重定向到 {1} doclet.duplicate.comment.for.property=属性注释重复。\n删除属性字段或此方法的注释以隐藏此警告。 +doclet.contains.diagnostic.markers=生成的文档包含无效输入的诊断标记。 #Documentation for Enums doclet.enum_values_doc.fullbody=返回包含该枚举类型的常量的数组,\n顺序与声明这些常量的顺序相同 diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/tool/resources/javadoc_de.properties b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/tool/resources/javadoc_de.properties index f8c3c086390..2192bfb3395 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/tool/resources/javadoc_de.properties +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/tool/resources/javadoc_de.properties @@ -159,6 +159,8 @@ main.opt.add.reads.desc=Gibt weitere Module an, die als von einem angegebenen Mo main.opt.patch.module.arg==(:)* main.opt.patch.module.desc=Setzt ein Modul außer Kraft oder erweitert es mit Klassen\nund Ressourcen in JAR-Dateien oder Verzeichnissen +main.opt.disable.line.doc.comments.desc=Unterstützung für Dokumentationskommentare mit Zeilen,\ndie mit "///" beginnen, deaktivieren + main.Xusage.foot=\nDies sind keine Standardoptionen, und sie können ohne Vorankündigung geändert werden. main.doclet.usage.header=\nBereitgestellt vom Doclet {0}: diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/tool/resources/javadoc_ja.properties b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/tool/resources/javadoc_ja.properties index 725497fecab..1cafe4d6ad5 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/tool/resources/javadoc_ja.properties +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/tool/resources/javadoc_ja.properties @@ -159,6 +159,8 @@ main.opt.add.reads.desc=指定のモジュールで必須とみなされるよ main.opt.patch.module.arg==(:)* main.opt.patch.module.desc=JARファイルまたはディレクトリのクラスおよびリソースでモジュールを\n オーバーライドまたは拡張します +main.opt.disable.line.doc.comments.desc='///'で行を開始すると、ドキュメンテーション・コメントのサポートが\n無効化されます + main.Xusage.foot=\nこれらは非標準オプションであり予告なしに変更されることがあります。 main.doclet.usage.header=\n{0} docletにより提供されるもの: diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/tool/resources/javadoc_zh_CN.properties b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/tool/resources/javadoc_zh_CN.properties index 41d065f5b1c..4ad91361e06 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/tool/resources/javadoc_zh_CN.properties +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/tool/resources/javadoc_zh_CN.properties @@ -159,6 +159,8 @@ main.opt.add.reads.desc=指定被视为给定模块需要的其他模块。\n< main.opt.patch.module.arg=<模块>=<文件>(:<文件>)* main.opt.patch.module.desc=使用\nJAR 文件或目录中的类和资源覆盖\n 或增强模块 +main.opt.disable.line.doc.comments.desc=禁用对带有以 '///' 开头的行的文档注释\n的支持 + main.Xusage.foot=\n这些选项都是非标准选项, 如有更改, 恕不另行通知。 main.doclet.usage.header=\n由 {0} doclet 提供: diff --git a/src/jdk.jconsole/share/classes/sun/tools/jconsole/resources/messages_de.properties b/src/jdk.jconsole/share/classes/sun/tools/jconsole/resources/messages_de.properties index b0efddf5ed3..e6859fd8d74 100644 --- a/src/jdk.jconsole/share/classes/sun/tools/jconsole/resources/messages_de.properties +++ b/src/jdk.jconsole/share/classes/sun/tools/jconsole/resources/messages_de.properties @@ -282,7 +282,7 @@ USERNAME_ACCESSIBLE_NAME=Benutzername USER_DATA=UserData VIRTUAL_MACHINE=Virtuelle Maschine VM_ARGUMENTS=VM-Argumente -VMINTERNAL_FRAME_ACCESSIBLE_DESCRIPTION=Innerer Frame für die Überwachung einer Java Virtual Machine +VMINTERNAL_FRAME_ACCESSIBLE_DESCRIPTION=Innerer Frame für das Monitoring einer Java Virtual Machine VALUE=Wert VENDOR=Hersteller VERBOSE_OUTPUT=Verbose-Ausgabe diff --git a/src/jdk.jdeps/share/classes/com/sun/tools/javap/resources/javap_de.properties b/src/jdk.jdeps/share/classes/com/sun/tools/javap/resources/javap_de.properties index 44265e8a6a5..1c98b4cac6a 100644 --- a/src/jdk.jdeps/share/classes/com/sun/tools/javap/resources/javap_de.properties +++ b/src/jdk.jdeps/share/classes/com/sun/tools/javap/resources/javap_de.properties @@ -100,6 +100,8 @@ main.opt.constants=\ -constants Zeigt die endgültigen Ko main.opt.sysinfo=\ -sysinfo Zeigt die Systeminformationen (Pfad, Größe, Datum, SHA-256-Hash)\n der Klasse, die verarbeitet wird +main.opt.verify=\ -verify Gibt zusätzliche Klassenverifizierungsinformationen aus + main.opt.module=\ --module -m Gibt das Modul an, das die zu disassemblierenden Klassen enthält main.opt.J=\ -J Gibt eine VM-Option an diff --git a/src/jdk.jdeps/share/classes/com/sun/tools/javap/resources/javap_ja.properties b/src/jdk.jdeps/share/classes/com/sun/tools/javap/resources/javap_ja.properties index 01e587336c5..4cc9e83070d 100644 --- a/src/jdk.jdeps/share/classes/com/sun/tools/javap/resources/javap_ja.properties +++ b/src/jdk.jdeps/share/classes/com/sun/tools/javap/resources/javap_ja.properties @@ -100,6 +100,8 @@ main.opt.constants=\ -constants final定数を表示す main.opt.sysinfo=\ -sysinfo 処理しているクラスのシステム情報(パス、サイズ、日付、SHA-256ハッシュ)\n を表示します +main.opt.verify=\ -verify クラスの検証情報が追加で出力されます + main.opt.module=\ --module -m 逆アセンブルされるクラスを含むモジュールを指定する main.opt.J=\ -J VMオプションを指定する diff --git a/src/jdk.jdeps/share/classes/com/sun/tools/javap/resources/javap_zh_CN.properties b/src/jdk.jdeps/share/classes/com/sun/tools/javap/resources/javap_zh_CN.properties index 57e3e6ac2eb..77c2fdd6b63 100644 --- a/src/jdk.jdeps/share/classes/com/sun/tools/javap/resources/javap_zh_CN.properties +++ b/src/jdk.jdeps/share/classes/com/sun/tools/javap/resources/javap_zh_CN.properties @@ -100,6 +100,8 @@ main.opt.constants=\ -constants 显示最终常量 main.opt.sysinfo=\ -sysinfo 显示正在处理的类的\n 系统信息(路径、大小、日期、SHA-256 散列) +main.opt.verify=\ -verify 输出其他类验证信息 + main.opt.module=\ --module -m 指定包含要反汇编的类的模块 main.opt.J=\ -J 指定 VM 选项 diff --git a/src/jdk.jdi/share/classes/com/sun/tools/example/debug/tty/TTYResources_de.java b/src/jdk.jdi/share/classes/com/sun/tools/example/debug/tty/TTYResources_de.java index ee6449f7605..04ec27a624d 100644 --- a/src/jdk.jdi/share/classes/com/sun/tools/example/debug/tty/TTYResources_de.java +++ b/src/jdk.jdi/share/classes/com/sun/tools/example/debug/tty/TTYResources_de.java @@ -322,7 +322,7 @@ public Object[][] getContents() { {"Unable to set", "{0} kann nicht festgelegt werden: {1}"}, {"Unexpected event type", "Unerwarteter Ereignistyp: {0}"}, {"unknown", "unbekannt"}, - {"Unmonitoring", "\u00DCberwachung von {0} aufheben"}, + {"Unmonitoring", "Monitoring von {0} aufheben"}, {"Unrecognized command. Try help...", "Nicht erkannter Befehl: \"{0}\". Rufen Sie \"help\" auf..."}, {"Usage: catch exception", "Verwendung: catch [uncaught|caught|all] |"}, {"Usage: ignore exception", "Verwendung: ignore [uncaught|caught|all] |"}, diff --git a/src/jdk.jdwp.agent/unix/native/libjdwp/exec_md.c b/src/jdk.jdwp.agent/unix/native/libjdwp/exec_md.c index a694bba93c4..a0f2687a88c 100644 --- a/src/jdk.jdwp.agent/unix/native/libjdwp/exec_md.c +++ b/src/jdk.jdwp.agent/unix/native/libjdwp/exec_md.c @@ -35,14 +35,14 @@ #include "error_messages.h" static char *skipWhitespace(char *p) { - while ((*p != '\0') && isspace(*p)) { + while ((*p != '\0') && isspace((unsigned char) *p)) { p++; } return p; } static char *skipNonWhitespace(char *p) { - while ((*p != '\0') && !isspace(*p)) { + while ((*p != '\0') && !isspace((unsigned char) *p)) { p++; } return p; diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/HiddenWait.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/HiddenWait.java new file mode 100644 index 00000000000..26505990332 --- /dev/null +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/HiddenWait.java @@ -0,0 +1,32 @@ +/* + * 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.jfr.internal; + +/** + * The HiddenWait class is used to exclude jdk.JavaMonitorWait events + * from being generated when Object.wait() is called on an object of this type. + */ +public final class HiddenWait { +} diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/JVM.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/JVM.java index 02edfd1e5ab..2e600c8c029 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/JVM.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/JVM.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 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,14 +41,10 @@ public final class JVM { static final long RESERVED_CLASS_ID_LIMIT = 500; - private static class ChunkRotationMonitor {} - /* * The JVM uses the chunk rotation monitor to notify Java that a rotation is warranted. - * The monitor type is used to exclude jdk.JavaMonitorWait events from being generated - * when Object.wait() is called on this monitor. */ - public static final Object CHUNK_ROTATION_MONITOR = new ChunkRotationMonitor(); + public static final Object CHUNK_ROTATION_MONITOR = new HiddenWait(); private static volatile boolean nativeOK; @@ -174,6 +170,11 @@ private static class ChunkRotationMonitor {} */ public static native long getTicksFrequency(); + /** + * Returns the same clock that sets the start time of a chunk (in nanos). + */ + public static native long nanosNow(); + /** * Write message to log. Should swallow null or empty message, and be able * to handle any Java character and not crash with very large message diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/JVMSupport.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/JVMSupport.java index 036d49e6e0e..345d2fdcc8d 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/JVMSupport.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/JVMSupport.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 @@ -119,11 +119,7 @@ private static void awaitUniqueTimestamp() { lastTimestamp = time; return; } - try { - Thread.sleep(0, 100); - } catch (InterruptedException iex) { - // ignore - } + Utils.takeNap(1); } } diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/MetadataRepository.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/MetadataRepository.java index 7031718cb28..75be70a0d1d 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/MetadataRepository.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/MetadataRepository.java @@ -64,6 +64,8 @@ public final class MetadataRepository { private boolean unregistered; private long lastUnloaded = -1; + private long lastMillis; + public MetadataRepository() { initializeJVMEventTypes(); } @@ -313,11 +315,12 @@ synchronized Instant setOutput(String filename) { if (staleMetadata) { storeDescriptorInJVM(); } + // Each chunk needs a unique timestamp. If two chunks get the same + // timestamp, the parser may stop prematurely at an earlier chunk. + // The resolution needs to be measured in milliseconds as this + // is what RecordingInfo:getStopTime() returns. + awaitEpochMilliShift(); JVM.setOutput(filename); - // Each chunk needs a unique start timestamp and - // if the clock resolution is low, two chunks may - // get the same timestamp. Utils.getChunkStartNanos() - // ensures the timestamp is unique for the next chunk long chunkStart = JVMSupport.getChunkStartNanos(); if (filename != null) { RepositoryFiles.notifyNewFile(); @@ -332,6 +335,18 @@ synchronized Instant setOutput(String filename) { return Utils.epochNanosToInstant(chunkStart); } + private void awaitEpochMilliShift() { + while (true) { + long nanos = JVM.nanosNow(); + long millis = Utils.epochNanosToInstant(nanos).toEpochMilli(); + if (millis != lastMillis) { + lastMillis = millis; + return; + } + Utils.takeNap(1); + } + } + private void unregisterUnloaded() { long unloaded = JVM.getUnloadedEventClassCount(); if (this.lastUnloaded != unloaded) { diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/AbstractEventStream.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/AbstractEventStream.java index e3b43635e33..cdfc8f017a6 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/AbstractEventStream.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/AbstractEventStream.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2022, 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 @@ -205,7 +205,7 @@ public final void awaitTermination(Duration timeout) throws InterruptedException protected abstract void process() throws IOException; - protected abstract boolean isRecording(); + protected abstract boolean isRecordingStream(); protected final void closeParser() { parserState.close(); @@ -249,7 +249,7 @@ private void startInternal(long startNanos) { if (streamConfiguration.started) { throw new IllegalStateException("Event stream can only be started once"); } - if (isRecording() && streamConfiguration.startTime == null) { + if (isRecordingStream() && streamConfiguration.startTime == null) { streamConfiguration.setStartNanos(startNanos); } streamConfiguration.setStarted(true); diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/EventDirectoryStream.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/EventDirectoryStream.java index 3ed1c4c8d35..bc9aa7987c3 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/EventDirectoryStream.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/EventDirectoryStream.java @@ -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 @@ -33,9 +33,11 @@ import java.util.Comparator; import java.util.List; import java.util.Objects; +import java.util.concurrent.atomic.AtomicLong; import java.util.function.Consumer; import jdk.jfr.Configuration; +import jdk.jfr.RecordingState; import jdk.jfr.consumer.RecordedEvent; import jdk.jfr.internal.JVM; import jdk.jfr.internal.LogLevel; @@ -59,6 +61,7 @@ public final class EventDirectoryStream extends AbstractEventStream { private final FileAccess fileAccess; private final PlatformRecording recording; private final StreamBarrier barrier = new StreamBarrier(); + private final AtomicLong streamId = new AtomicLong(); private ChunkParser currentParser; private long currentChunkStartNanos; private RecordedEvent[] sortedCache; @@ -80,6 +83,8 @@ public EventDirectoryStream( } this.fileAccess = Objects.requireNonNull(fileAccess); this.repositoryFiles = new RepositoryFiles(fileAccess, p, allowSubDirectories); + this.streamId.incrementAndGet(); + Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.INFO, "Stream " + streamId + " started."); } @Override @@ -137,13 +142,14 @@ protected void processRecursionSafe() throws IOException { Dispatcher lastDisp = null; Dispatcher disp = dispatcher(); Path path; - boolean validStartTime = isRecording() || disp.startTime != null; + boolean validStartTime = isRecordingStream() || disp.startTime != null; if (validStartTime) { path = repositoryFiles.firstPath(disp.startNanos, true); } else { path = repositoryFiles.lastPath(true); } if (path == null) { // closed + logStreamEnd("no first chunk file found."); return; } currentChunkStartNanos = repositoryFiles.getTimestamp(path); @@ -168,7 +174,10 @@ protected void processRecursionSafe() throws IOException { processUnordered(disp); } currentParser.resetCache(); - if (currentParser.getLastFlush() > filterEnd) { + long lastFlush = currentParser.getLastFlush(); + if (lastFlush > filterEnd) { + logStreamEnd("end time at " + filterEnd + + "ns (epoch), parser at " + lastFlush + "ns (epoch)."); return; } } @@ -177,20 +186,25 @@ protected void processRecursionSafe() throws IOException { barrier.check(); // block if recording is being stopped if (barrier.getStreamEnd() <= endMillis) { + String msg = "stopped at " + barrier.getStreamEnd() + "ms (epoch), "; + msg += "parser at " + endMillis + "ms (epoch), " + endNanos + "ns (epoch)"; + logStreamEnd(msg); return; } - if (!barrier.hasStreamEnd() && isLastChunk()) { - // Recording was stopped/closed externally, and no more data to process. - return; + if (isRecordingStream()) { + if (recording.getState() == RecordingState.STOPPED && !barrier.used()) { + logStreamEnd("recording stopped externally."); + return; + } } if (repositoryFiles.hasFixedPath() && currentParser.isFinalChunk()) { - // JVM process exited/crashed, or repository migrated to an unknown location + logStreamEnd("JVM process exited/crashed, or repository migrated to an unknown location."); return; } if (isClosed()) { - // Stream was closed + logStreamEnd("stream closed."); return; } long durationNanos = currentParser.getChunkDuration(); @@ -205,7 +219,8 @@ protected void processRecursionSafe() throws IOException { } path = repositoryFiles.nextPath(currentChunkStartNanos + durationNanos, true); if (path == null) { - return; // stream closed + logStreamEnd("no more chunk files found."); + return; } currentChunkStartNanos = repositoryFiles.getTimestamp(path); input.setFile(path); @@ -217,15 +232,12 @@ protected void processRecursionSafe() throws IOException { } } - - private boolean isLastChunk() { - if (!isRecording()) { - return false; - } - return recording.getFinalChunkStartNanos() >= currentParser.getStartNanos(); + private void logStreamEnd(String text) { + String msg = "Stream " + streamId + " ended, " + text; + Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.INFO, msg); } - protected boolean isRecording() { + protected boolean isRecordingStream() { return recording != null; } diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/EventFileStream.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/EventFileStream.java index 9ecaf2a6075..f03e8d8acb4 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/EventFileStream.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/EventFileStream.java @@ -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 @@ -73,7 +73,7 @@ public void close() { } @Override - protected boolean isRecording() { + protected boolean isRecordingStream() { return false; } diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/management/StreamBarrier.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/management/StreamBarrier.java index ed94a908d47..0b3132da217 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/management/StreamBarrier.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/management/StreamBarrier.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 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 @@ public final class StreamBarrier implements Closeable { private boolean activated = false; + private boolean used = false; private long end = Long.MAX_VALUE; // Blocks thread until barrier is deactivated @@ -62,12 +63,9 @@ public synchronized long getStreamEnd() { return end; } - public synchronized boolean hasStreamEnd() { - return end != Long.MAX_VALUE; - } - public synchronized void activate() { activated = true; + used = true; } @Override @@ -75,4 +73,11 @@ public synchronized void close() throws IOException { activated = false; this.notifyAll(); } + + /** + * Returns {@code true) if barrier is, or has been, in active state, {@code false) otherwise. + */ + public synchronized boolean used() { + return used; + } } diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/util/Utils.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/util/Utils.java index 0049662e9a1..5e04a25fe7d 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/util/Utils.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/util/Utils.java @@ -48,6 +48,7 @@ import jdk.jfr.Event; import jdk.jfr.EventType; import jdk.jfr.RecordingState; +import jdk.jfr.internal.HiddenWait; import jdk.jfr.internal.LogLevel; import jdk.jfr.internal.LogTag; import jdk.jfr.internal.Logger; @@ -351,8 +352,11 @@ private static boolean isSupportedType(Class type) { } public static void takeNap(long millis) { + HiddenWait hiddenWait = new HiddenWait(); try { - Thread.sleep(millis); + synchronized(hiddenWait) { + hiddenWait.wait(millis); + } } catch (InterruptedException e) { // ok } diff --git a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/resources/MacResources_de.properties b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/resources/MacResources_de.properties index 3dc883b0728..0d2cfb5b6c0 100644 --- a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/resources/MacResources_de.properties +++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/resources/MacResources_de.properties @@ -94,6 +94,6 @@ message.preparing-distribution-dist=distribution.dist wird vorbereitet: {0}. message.signing.pkg=Warnung: Zum Signieren von PKG müssen Sie möglicherweise mit dem Schlüsselbundverwaltungstool die Option "Immer vertrauen" für Ihr Zertifikat festlegen. message.setfile.dmg=Das Festlegen des benutzerdefinierten Symbols für die DMG-Datei wurde übersprungen, weil das Utility "SetFile" nicht gefunden wurde. Durch Installieren von Xcode mit Befehlszeilentools sollte dieses Problem behoben werden. message.install-dir-ignored=Warnung: "--install-dir" wird von DMG nicht unterstützt. Stattdessen wird standardmäßig /Applications verwendet. -message.codesign.failed.reason.app.content="codesign" failed and additional application content was supplied via the "--app-content" parameter. Probably the additional content broke the integrity of the application bundle and caused the failure. Ensure content supplied via the "--app-content" parameter does not break the integrity of the application bundle, or add it in the post-processing step. +message.codesign.failed.reason.app.content="codesign" war nicht erfolgreich, und zusätzlicher Anwendungsinhalt wurde über den Parameter "--app-content" angegeben. Wahrscheinlich hat der zusätzliche Inhalt die Integrität des Anwendungs-Bundles beeinträchtigt und den Fehler verursacht. Stellen Sie sicher, das der über den Parameter "--app-content" angegebene Inhalt nicht die Integrität des Anwendungs-Bundles beeinträchtigt, oder fügen Sie ihn im Nachverarbeitungsschritt hinzu. warning.unsigned.app.image=Warnung: Nicht signiertes app-image wird zum Erstellen von signiertem {0} verwendet. warning.per.user.app.image.signed=Warnung: Konfiguration der installierten Anwendung pro Benutzer wird nicht unterstützt, da "{0}" im vordefinierten signierten Anwendungsimage fehlt. diff --git a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/resources/MacResources_ja.properties b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/resources/MacResources_ja.properties index f1359c0eaaa..08e6dd2b4e8 100644 --- a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/resources/MacResources_ja.properties +++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/resources/MacResources_ja.properties @@ -94,6 +94,6 @@ message.preparing-distribution-dist=distribution.distを準備しています: { message.signing.pkg=警告: PKGへの署名の場合、「キーチェーン・アクセス」ツールを使用して証明書に「常に信頼する」を設定する必要があります。 message.setfile.dmg='SetFile'ユーティリティが見つからないため、DMGファイルでのカスタム・アイコンの設定がスキップされました。Xcodeとコマンド・ライン・ツールをインストールすると、この問題は解決されます。 message.install-dir-ignored=警告: "--install-dir"はDMGでサポートされていません。/Applicationsにデフォルト設定されます。 -message.codesign.failed.reason.app.content="codesign" failed and additional application content was supplied via the "--app-content" parameter. Probably the additional content broke the integrity of the application bundle and caused the failure. Ensure content supplied via the "--app-content" parameter does not break the integrity of the application bundle, or add it in the post-processing step. +message.codesign.failed.reason.app.content="codesign"が失敗したため、追加のアプリケーション・コンテンツが、"--app-content"パラメータを介して提供されました。追加のコンテンツにより、アプリケーション・バンドルの整合性が損われ、失敗の原因になった可能性があります。"--app-content"パラメータを介して提供されたコンテンツによって、アプリケーション・バンドルの整合性が損われていないことを確認するか、処理後のステップで追加してください。 warning.unsigned.app.image=警告: 署名されていないapp-imageを使用して署名された{0}を作成します。 warning.per.user.app.image.signed=警告: 事前定義済の署名付きアプリケーション・イメージに"{0}"がないため、インストール済アプリケーションのユーザーごとの構成はサポートされません。 diff --git a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/resources/MacResources_zh_CN.properties b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/resources/MacResources_zh_CN.properties index eeb49d6e591..f4e75caa1ef 100644 --- a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/resources/MacResources_zh_CN.properties +++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/resources/MacResources_zh_CN.properties @@ -94,6 +94,6 @@ message.preparing-distribution-dist=正在准备 distribution.dist: {0}。 message.signing.pkg=警告:要对 PKG 进行签名,可能需要使用“密钥链访问”工具为证书设置“始终信任”。 message.setfile.dmg=由于未找到 'SetFile' 实用程序,跳过了针对 DMG 文件设置定制图标的操作。安装带命令行工具的 Xcode 应能解决此问题。 message.install-dir-ignored=警告:"--install-dir" 不受 DMG 支持,将默认为 /Applications。 -message.codesign.failed.reason.app.content="codesign" failed and additional application content was supplied via the "--app-content" parameter. Probably the additional content broke the integrity of the application bundle and caused the failure. Ensure content supplied via the "--app-content" parameter does not break the integrity of the application bundle, or add it in the post-processing step. +message.codesign.failed.reason.app.content="codesign" 失败,并通过 "--app-content" 参数提供了附加应用程序内容。可能是附加内容破坏了应用程序包的完整性,导致了故障。请确保通过 "--app-content" 参数提供的内容不会破坏应用程序包的完整性,或者在后处理步骤中添加该内容。 warning.unsigned.app.image=警告:使用未签名的 app-image 生成已签名的 {0}。 warning.per.user.app.image.signed=警告:由于预定义的已签名应用程序映像中缺少 "{0}",不支持对已安装应用程序的每用户配置提供支持。 diff --git a/src/jdk.jpackage/share/native/common/ErrorHandling.cpp b/src/jdk.jpackage/share/native/common/ErrorHandling.cpp index b3f7206bfc1..a3a4c2a9dfe 100644 --- a/src/jdk.jpackage/share/native/common/ErrorHandling.cpp +++ b/src/jdk.jpackage/share/native/common/ErrorHandling.cpp @@ -72,7 +72,7 @@ std::string makeMessage(const std::runtime_error& e, const SourceCodePos& pos) { namespace { bool isNotSpace(int chr) { - return isspace(chr) == 0; + return isspace((unsigned char) chr) == 0; } diff --git a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/WinResources_de.properties b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/WinResources_de.properties index dce4a911fe1..e0c15bfb8fb 100644 --- a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/WinResources_de.properties +++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/WinResources_de.properties @@ -59,7 +59,7 @@ error.read-wix-l10n-file=Datei {0} konnte nicht geparst werden error.extract-culture-from-wix-l10n-file=Kulturwert konnte nicht aus Datei {0} gelesen werden message.icon-not-ico=Das angegebene Symbol "{0}" ist keine ICO-Datei und wird nicht verwendet. Stattdessen wird das Standardsymbol verwendet. -message.potential.windows.defender.issue=Warnung: Windows Defender verhindert eventuell die korrekte Ausführung von jpackage. Wenn ein Problem auftritt, deaktivieren Sie die Echtzeitüberwachung, oder fügen Sie einen Ausschluss für das Verzeichnis "{0}" hinzu. +message.potential.windows.defender.issue=Warnung: Windows Defender verhindert eventuell die korrekte Ausführung von jpackage. Wenn ein Problem auftritt, deaktivieren Sie das Echtzeitmonitoring, oder fügen Sie einen Ausschluss für das Verzeichnis "{0}" hinzu. message.outputting-to-location=EXE für Installationsprogramm wird generiert in: {0}. message.output-location=Installationsprogramm (.exe) gespeichert in: {0} message.tool-version=[{0}]-Version [{1}] erkannt. diff --git a/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/resources/l10n_de.properties b/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/resources/l10n_de.properties index 50c7515e9d8..21f30891008 100644 --- a/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/resources/l10n_de.properties +++ b/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/resources/l10n_de.properties @@ -168,6 +168,7 @@ jshell.console.completion.all.completions = jshell.console.do.nothing = Nichts machen jshell.console.choice = Auswahl:\u0020 + jshell.console.create.variable = Variable erstellen jshell.console.create.method = Methode erstellen jshell.console.resolvable = \nDie ID kann in diesem Kontext aufgelöst werden. @@ -179,7 +180,7 @@ jshell.console.empty = \nLeerer Eintrag. Ein einzelner gültiger Ausdruck oder e jshell.fix.wrong.shortcut =Unerwartetes Zeichen nach Umschalt+Tab.\nVerwenden Sie "I" für automatischen Import, "V" zur Variablenerstellung oder "M" zur Methodenerstellung.\nWeitere Informationen finden Sie unter:\n/help shortcuts -help.usage = Verwendung: jshell